diff --git a/CHANGELOG.md b/CHANGELOG.md index 728df487dd8..ac1fe0d0e37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 6.1.0 +- Bug Fix: Context.PolicyKey behaviour in PolicyWrap (issue 463) +- Bug Fix: CachePolicy behaviour with non-nullable types (issues 472, 475) +- Enhancement: WaitAnd/RetryForever overloads where onRetry takes the retry number as a parameter (issue 451) +- Enhancement: Overloads where onTimeout takes thrown exception as a parameter (issue 338) +- Enhancement: Improved cache error message (issue 455) + ## 6.0.1 - Version 6 RTM, for integration to ASPNET Core 2.1 IHttpClientFactory diff --git a/GitVersionConfig.yaml b/GitVersionConfig.yaml index 67f30281d5d..315a5d48dfa 100644 --- a/GitVersionConfig.yaml +++ b/GitVersionConfig.yaml @@ -1 +1 @@ -next-version: 6.0.1 \ No newline at end of file +next-version: 6.1.0 \ No newline at end of file diff --git a/README.md b/README.md index 31accd66095..f30acbad39e 100644 --- a/README.md +++ b/README.md @@ -309,7 +309,9 @@ Policy }); ``` -For more detail see: [Retry policy documentation](https://github.com/App-vNext/Polly/wiki/Retry) on wiki. +If all retries fail, a retry policy rethrows the final exception back to the calling code. + +For more depth see also: [Retry policy documentation on wiki](https://github.com/App-vNext/Polly/wiki/Retry). ### Circuit Breaker @@ -355,7 +357,10 @@ breaker.Isolate(); breaker.Reset(); ``` -For more detail see: [Circuit-Breaker documentation](https://github.com/App-vNext/Polly/wiki/Circuit-Breaker) on wiki. + +Note that circuit-breaker policies [rethrow all exceptions](https://github.com/App-vNext/Polly/wiki/Circuit-Breaker#exception-handling), even handled ones. A circuit-breaker exists to measure faults and break the circuit when too many faults occur, but does not orchestrate retries. Combine a circuit-breaker with a retry policy as needed. + +For more depth see also: [Circuit-Breaker documentation on wiki](https://github.com/App-vNext/Polly/wiki/Circuit-Breaker). ### Advanced Circuit Breaker ```csharp @@ -589,15 +594,16 @@ For more detail see: [Bulkhead policy documentation](https://github.com/App-vNex var memoryCacheProvider = new MemoryCacheProvider(MemoryCache.Default); var cachePolicy = Policy.Cache(memoryCacheProvider, TimeSpan.FromMinutes(5)); +// For .NET Core examples see the CacheProviders linked to from https://github.com/App-vNext/Polly/wiki/Cache#working-with-cacheproviders : +// - https://github.com/App-vNext/Polly.Caching.MemoryCache +// - https://github.com/App-vNext/Polly.Caching.IDistributedCache + // Define a cache policy with absolute expiration at midnight tonight. var cachePolicy = Policy.Cache(memoryCacheProvider, new AbsoluteTtl(DateTimeOffset.Now.Date.AddDays(1)); // Define a cache policy with sliding expiration: items remain valid for another 5 minutes each time the cache item is used. var cachePolicy = Policy.Cache(memoryCacheProvider, new SlidingTtl(TimeSpan.FromMinutes(5)); -// Execute through the cache as a read-through cache: check the cache first; if not found, execute underlying delegate and store the result in the cache. -TResult result = cachePolicy.Execute(() => getFoo(), new Context("FooKey")); // "FooKey" is the cache key used in this execution. - // Define a cache Policy, and catch any cache provider errors for logging. var cachePolicy = Policy.Cache(myCacheProvider, TimeSpan.FromMinutes(5), (context, key, ex) => { @@ -605,6 +611,10 @@ var cachePolicy = Policy.Cache(myCacheProvider, TimeSpan.FromMinutes(5), } ); +// Execute through the cache as a read-through cache: check the cache first; if not found, execute underlying delegate and store the result in the cache. +// The key to use for caching, for a particular execution, is specified by setting the OperationKey (before v6: ExecutionKey) on a Context instance passed to the execution. Use an overload of the form shown below (or a richer overload including the same elements). +// Example: "FooKey" is the cache key that will be used in the below execution. +TResult result = cachePolicy.Execute(context => getFoo(), new Context("FooKey")); ``` @@ -772,7 +782,7 @@ var policy = Policy .WithPolicyKey("MyDataAccessPolicy"); int id = ... // customer id from somewhere -var customerDetails = policy.Execute(() => GetCustomer(id), +var customerDetails = policy.Execute(context => GetCustomer(id), new Context("GetCustomerDetails", new Dictionary() {{"Type","Customer"},{"Id",id}} )); ``` @@ -980,6 +990,8 @@ For full detailed of supported targets by version, see [supported targets](https * [@benagain](https://github.com/benagain) - Bug fix: RelativeTtl in CachePolicy now always returns a ttl relative to time item is cached. * [@urig](https://github.com/urig) - Allow TimeoutPolicy to be configured with Timeout.InfiniteTimeSpan. * [@reisenberger](https://github.com/reisenberger) - Integration with [IHttpClientFactory](https://github.com/aspnet/HttpClientFactory/) for ASPNET Core 2.1. +* [@freakazoid182](https://github.com/Freakazoid182) - WaitAnd/RetryForever overloads where onRetry takes the retry number as a parameter. +* [@dustyhoppe](https://github.com/dustyhoppe) - Overloads where onTimeout takes thrown exception as a parameter # Sample Projects diff --git a/src/Polly.NetStandard11/Properties/AssemblyInfo.cs b/src/Polly.NetStandard11/Properties/AssemblyInfo.cs index 8bea54c3b51..3d2471a1b80 100644 --- a/src/Polly.NetStandard11/Properties/AssemblyInfo.cs +++ b/src/Polly.NetStandard11/Properties/AssemblyInfo.cs @@ -3,8 +3,8 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTitle("Polly")] -[assembly: AssemblyInformationalVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyInformationalVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] [assembly: AssemblyVersion("6.0.0.0")] [assembly: CLSCompliant(true)] diff --git a/src/Polly.NetStandard20/Properties/AssemblyInfo.cs b/src/Polly.NetStandard20/Properties/AssemblyInfo.cs index 7d9cb58df33..7cd1b7d4483 100644 --- a/src/Polly.NetStandard20/Properties/AssemblyInfo.cs +++ b/src/Polly.NetStandard20/Properties/AssemblyInfo.cs @@ -3,8 +3,8 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTitle("Polly")] -[assembly: AssemblyInformationalVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyInformationalVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] [assembly: AssemblyVersion("6.0.0.0")] [assembly: CLSCompliant(true)] diff --git a/src/Polly.Shared/Caching/CachePolicy.cs b/src/Polly.Shared/Caching/CachePolicy.cs index f5438eaf57b..99c3a7e6567 100644 --- a/src/Polly.Shared/Caching/CachePolicy.cs +++ b/src/Polly.Shared/Caching/CachePolicy.cs @@ -55,6 +55,8 @@ internal CachePolicy( [DebuggerStepThrough] internal override TResult ExecuteInternal(Func action, Context context, CancellationToken cancellationToken) { + if (_syncCacheProvider == null) throw new InvalidOperationException("Please use the synchronous-defined policies when calling the synchronous Execute (and similar) methods."); + return CacheEngine.Implementation( _syncCacheProvider.For(), _ttlStrategy.For(), diff --git a/src/Polly.Shared/Caching/CachePolicyAsync.cs b/src/Polly.Shared/Caching/CachePolicyAsync.cs index b9fc3ef4ed2..c5e0b755df0 100644 --- a/src/Polly.Shared/Caching/CachePolicyAsync.cs +++ b/src/Polly.Shared/Caching/CachePolicyAsync.cs @@ -47,6 +47,8 @@ internal CachePolicy( [DebuggerStepThrough] internal override Task ExecuteAsyncInternal(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { + if (_asyncCacheProvider == null) throw new InvalidOperationException("Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods."); + return CacheEngine.ImplementationAsync( _asyncCacheProvider.AsyncFor(), _ttlStrategy.For(), diff --git a/src/Polly.Shared/Caching/GenericCacheProvider.cs b/src/Polly.Shared/Caching/GenericCacheProvider.cs index 51ffb8c3708..61f6f975a95 100644 --- a/src/Polly.Shared/Caching/GenericCacheProvider.cs +++ b/src/Polly.Shared/Caching/GenericCacheProvider.cs @@ -12,14 +12,12 @@ internal class GenericCacheProvider : ISyncCacheProvider.Get(string key) { - return (TCacheFormat) _wrappedCacheProvider.Get(key); + return (TCacheFormat) (_wrappedCacheProvider.Get(key) ?? default(TCacheFormat)); } void ISyncCacheProvider.Put(string key, TCacheFormat value, Ttl ttl) diff --git a/src/Polly.Shared/Caching/GenericCacheProviderAsync.cs b/src/Polly.Shared/Caching/GenericCacheProviderAsync.cs index 5cb371d6027..0499437b425 100644 --- a/src/Polly.Shared/Caching/GenericCacheProviderAsync.cs +++ b/src/Polly.Shared/Caching/GenericCacheProviderAsync.cs @@ -14,14 +14,12 @@ internal class GenericCacheProviderAsync : IAsyncCacheProvider IAsyncCacheProvider.GetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) { - return (TCacheFormat) await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return (TCacheFormat) (await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext) ?? default(TCacheFormat)); } Task IAsyncCacheProvider.PutAsync(string key, TCacheFormat value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) diff --git a/src/Polly.Shared/Caching/SerializingCacheProvider.cs b/src/Polly.Shared/Caching/SerializingCacheProvider.cs index e1909219d94..e30ba11d669 100644 --- a/src/Polly.Shared/Caching/SerializingCacheProvider.cs +++ b/src/Polly.Shared/Caching/SerializingCacheProvider.cs @@ -20,11 +20,8 @@ public class SerializingCacheProvider : ISyncCacheProvider /// serializer public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) { - if (wrappedCacheProvider == null) throw new ArgumentNullException(nameof(wrappedCacheProvider)); - if (serializer == null) throw new ArgumentNullException(nameof(serializer)); - - _wrappedCacheProvider = wrappedCacheProvider; - _serializer = serializer; + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } /// @@ -34,7 +31,8 @@ public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProv /// The value from cache; or null, if none was found. public object Get(string key) { - return _serializer.Deserialize(_wrappedCacheProvider.Get(key)); + TSerialized objectToDeserialize = _wrappedCacheProvider.Get(key); + return objectToDeserialize == null || objectToDeserialize.Equals(default(TSerialized)) ? null : _serializer.Deserialize(objectToDeserialize); } /// @@ -45,7 +43,8 @@ public object Get(string key) /// The time-to-live for the cache entry. public void Put(string key, object value, Ttl ttl) { - _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); + if (value != null) + _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); } } @@ -69,11 +68,8 @@ public class SerializingCacheProvider : ISyncCacheProvider /// serializer public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) { - if (wrappedCacheProvider == null) throw new ArgumentNullException(nameof(wrappedCacheProvider)); - if (serializer == null) throw new ArgumentNullException(nameof(serializer)); - - _wrappedCacheProvider = wrappedCacheProvider; - _serializer = serializer; + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } /// @@ -83,7 +79,8 @@ public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProv /// The value from cache; or null, if none was found. public TResult Get(string key) { - return _serializer.Deserialize(_wrappedCacheProvider.Get(key)); + TSerialized objectToDeserialize = _wrappedCacheProvider.Get(key); + return objectToDeserialize == null || objectToDeserialize.Equals(default(TSerialized)) ? default(TResult) : _serializer.Deserialize(objectToDeserialize); } /// @@ -94,7 +91,8 @@ public TResult Get(string key) /// The time-to-live for the cache entry. public void Put(string key, TResult value, Ttl ttl) { - _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); + if (value != null && !value.Equals(default(TResult))) + _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); } } diff --git a/src/Polly.Shared/Caching/SerializingCacheProviderAsync.cs b/src/Polly.Shared/Caching/SerializingCacheProviderAsync.cs index 788fbea802c..14d3908d7fd 100644 --- a/src/Polly.Shared/Caching/SerializingCacheProviderAsync.cs +++ b/src/Polly.Shared/Caching/SerializingCacheProviderAsync.cs @@ -22,11 +22,8 @@ public class SerializingCacheProviderAsync : IAsyncCacheProvider /// serializer public SerializingCacheProviderAsync(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) { - if (wrappedCacheProvider == null) throw new ArgumentNullException(nameof(wrappedCacheProvider)); - if (serializer == null) throw new ArgumentNullException(nameof(serializer)); - - _wrappedCacheProvider = wrappedCacheProvider; - _serializer = serializer; + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } /// @@ -38,8 +35,8 @@ public SerializingCacheProviderAsync(IAsyncCacheProvider wrappedCac /// A promising as Result the value from cache; or null, if none was found. public async Task GetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) { - return _serializer.Deserialize( - await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext)); + TSerialized objectToDeserialize = await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return objectToDeserialize == null || objectToDeserialize.Equals(default(TSerialized)) ? null :_serializer.Deserialize(objectToDeserialize); } /// @@ -54,7 +51,9 @@ public async Task GetAsync(string key, CancellationToken cancellationTok public async Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) { - await _wrappedCacheProvider.PutAsync( + if (value != null) + + await _wrappedCacheProvider.PutAsync( key, _serializer.Serialize(value), ttl, @@ -83,11 +82,8 @@ public class SerializingCacheProviderAsync : IAsyncCachePr /// serializer public SerializingCacheProviderAsync(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) { - if (wrappedCacheProvider == null) throw new ArgumentNullException(nameof(wrappedCacheProvider)); - if (serializer == null) throw new ArgumentNullException(nameof(serializer)); - - _wrappedCacheProvider = wrappedCacheProvider; - _serializer = serializer; + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } /// @@ -99,8 +95,8 @@ public SerializingCacheProviderAsync(IAsyncCacheProvider wrappedCac /// A promising as Result the value from cache; or null, if none was found. public async Task GetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) { - return _serializer.Deserialize( - await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext)); + TSerialized objectToDeserialize = await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return objectToDeserialize == null || objectToDeserialize.Equals(default(TSerialized)) ? default(TResult) : _serializer.Deserialize(objectToDeserialize); } /// @@ -115,7 +111,9 @@ public async Task GetAsync(string key, CancellationToken cancellationTo public async Task PutAsync(string key, TResult value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) { - await _wrappedCacheProvider.PutAsync( + if (value != null && !value.Equals(default(TResult))) + + await _wrappedCacheProvider.PutAsync( key, _serializer.Serialize(value), ttl, diff --git a/src/Polly.Shared/Policy.Async.ExecuteOverloads.cs b/src/Polly.Shared/Policy.Async.ExecuteOverloads.cs index 32f04cdd891..9a1556b6c5a 100644 --- a/src/Polly.Shared/Policy.Async.ExecuteOverloads.cs +++ b/src/Polly.Shared/Policy.Async.ExecuteOverloads.cs @@ -117,9 +117,17 @@ public Task ExecuteAsync(Func action, IDiction public Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); - return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + + try + { + return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #region Overloads method-generic in TResult @@ -245,9 +253,17 @@ public Task ExecuteAsync(Func ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); - return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + + try + { + return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #endregion diff --git a/src/Polly.Shared/Policy.Async.TResult.ExecuteOverloads.cs b/src/Polly.Shared/Policy.Async.TResult.ExecuteOverloads.cs index 3b50b32be34..8d6033297fd 100644 --- a/src/Polly.Shared/Policy.Async.TResult.ExecuteOverloads.cs +++ b/src/Polly.Shared/Policy.Async.TResult.ExecuteOverloads.cs @@ -128,9 +128,17 @@ public Task ExecuteAsync(Func public Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); - return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + + try + { + return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #endregion diff --git a/src/Polly.Shared/Policy.ContextAndKeys.cs b/src/Polly.Shared/Policy.ContextAndKeys.cs index 21d980b898a..c107474276e 100644 --- a/src/Polly.Shared/Policy.ContextAndKeys.cs +++ b/src/Polly.Shared/Policy.ContextAndKeys.cs @@ -21,10 +21,27 @@ public abstract partial class PolicyBase /// Updates the execution with context from the executing . /// /// The execution . - internal virtual void SetPolicyContext(Context executionContext) + /// 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; } + + /// + /// 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; + } } public abstract partial class Policy diff --git a/src/Polly.Shared/Policy.ExecuteOverloads.cs b/src/Polly.Shared/Policy.ExecuteOverloads.cs index b3422564ce5..d7c2d23cc95 100644 --- a/src/Polly.Shared/Policy.ExecuteOverloads.cs +++ b/src/Polly.Shared/Policy.ExecuteOverloads.cs @@ -77,9 +77,16 @@ public void Execute(Action action, Context context, { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); - ExecuteInternal(action, context, cancellationToken); + try + { + ExecuteInternal(action, context, cancellationToken); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #region Overloads method-generic in TResult @@ -171,9 +178,16 @@ public TResult Execute(Func action { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); - return ExecuteInternal(action, context, cancellationToken); + try + { + return ExecuteInternal(action, context, cancellationToken); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #endregion diff --git a/src/Polly.Shared/Policy.TResult.ExecuteOverloads.cs b/src/Polly.Shared/Policy.TResult.ExecuteOverloads.cs index b97541907a9..6dab4628c9e 100644 --- a/src/Polly.Shared/Policy.TResult.ExecuteOverloads.cs +++ b/src/Polly.Shared/Policy.TResult.ExecuteOverloads.cs @@ -92,9 +92,17 @@ public TResult Execute(Func action, IDictio public TResult Execute(Func action, Context context, CancellationToken cancellationToken) { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); - return ExecuteInternal(action, context, cancellationToken); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + + try + { + return ExecuteInternal(action, context, cancellationToken); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #endregion diff --git a/src/Polly.Shared/Polly.Shared.projitems b/src/Polly.Shared/Polly.Shared.projitems index 21c11d3ad99..9ec35cfe8b4 100644 --- a/src/Polly.Shared/Polly.Shared.projitems +++ b/src/Polly.Shared/Polly.Shared.projitems @@ -119,6 +119,10 @@ + + + + diff --git a/src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs new file mode 100644 index 00000000000..b04137b9b79 --- /dev/null +++ b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading; + +namespace Polly.Retry +{ + internal partial class RetryStateRetryForeverWithCount : IRetryPolicyState + { + private int _errorCount; + private readonly Action, int, Context> _onRetry; + private readonly Context _context; + + public RetryStateRetryForeverWithCount(Action, int, Context> onRetry, Context context) + { + _onRetry = onRetry; + _context = context; + } + + public bool CanRetry(DelegateResult delegateResult, CancellationToken cancellationToken) + { + if (_errorCount < int.MaxValue) + { + _errorCount += 1; + } + + _onRetry(delegateResult, _errorCount, _context); + return true; + } + } +} \ No newline at end of file diff --git a/src/Polly.Shared/Retry/RetryStateRetryForeverWithCountAsync.cs b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCountAsync.cs new file mode 100644 index 00000000000..793a9f753ca --- /dev/null +++ b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCountAsync.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Polly.Retry +{ + internal partial class RetryStateRetryForeverWithCount : IRetryPolicyState + { + private readonly Func, int, Context, Task> _onRetryAsync; + + public RetryStateRetryForeverWithCount(Func, int, Context, Task> onRetryAsync, Context context) + { + _onRetryAsync = onRetryAsync; + _context = context; + } + + public async Task CanRetryAsync(DelegateResult delegateResult, CancellationToken ct, bool continueOnCapturedContext) + { + if (_errorCount < int.MaxValue) + { + _errorCount += 1; + } + + await _onRetryAsync(delegateResult, _errorCount, _context).ConfigureAwait(continueOnCapturedContext); + return true; + } + } +} + diff --git a/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCount.cs b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCount.cs new file mode 100644 index 00000000000..b189ca4814c --- /dev/null +++ b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCount.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; +using Polly.Utilities; + +namespace Polly.Retry +{ + internal partial class RetryStateWaitAndRetryForeverWithCount : IRetryPolicyState + { + private int _errorCount; + private readonly Func, Context, TimeSpan> _sleepDurationProvider; + private readonly Action, int, TimeSpan, Context> _onRetry; + private readonly Context _context; + + public RetryStateWaitAndRetryForeverWithCount(Func, Context, TimeSpan> sleepDurationProvider, Action, int, TimeSpan, Context> onRetry, Context context) + { + _sleepDurationProvider = sleepDurationProvider; + _onRetry = onRetry; + _context = context; + } + + public bool CanRetry(DelegateResult delegateResult, CancellationToken cancellationToken) + { + if (_errorCount < int.MaxValue) + { + _errorCount += 1; + } + + TimeSpan waitTimeSpan = _sleepDurationProvider(_errorCount, delegateResult, _context); + + _onRetry(delegateResult, _errorCount, waitTimeSpan, _context); + + SystemClock.Sleep(waitTimeSpan, cancellationToken); + + return true; + } + } +} diff --git a/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCountAsync.cs b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCountAsync.cs new file mode 100644 index 00000000000..b56fded5a13 --- /dev/null +++ b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCountAsync.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Polly.Utilities; + +namespace Polly.Retry +{ + internal partial class RetryStateWaitAndRetryForeverWithCount : IRetryPolicyState + { + private readonly Func, int, TimeSpan, Context, Task> _onRetryAsync; + + public RetryStateWaitAndRetryForeverWithCount(Func, Context, TimeSpan> sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync, Context context) + { + _sleepDurationProvider = sleepDurationProvider; + _onRetryAsync = onRetryAsync; + _context = context; + } + + public async Task CanRetryAsync(DelegateResult delegateResult, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + if (_errorCount < int.MaxValue) + { + _errorCount += 1; + } + + TimeSpan waitTimeSpan = _sleepDurationProvider(_errorCount, delegateResult, _context); + + await _onRetryAsync(delegateResult, _errorCount, waitTimeSpan, _context).ConfigureAwait(continueOnCapturedContext); + + await SystemClock.SleepAsync(waitTimeSpan, cancellationToken).ConfigureAwait(continueOnCapturedContext); + + return true; + } + } +} + diff --git a/src/Polly.Shared/Retry/RetrySyntax.cs b/src/Polly.Shared/Retry/RetrySyntax.cs index 2738e997415..f7d8a57aae2 100644 --- a/src/Polly.Shared/Retry/RetrySyntax.cs +++ b/src/Polly.Shared/Retry/RetrySyntax.cs @@ -128,7 +128,22 @@ public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action< { if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - return policyBuilder.RetryForever((outcome, ctx) => onRetry(outcome)); + return policyBuilder.RetryForever((Exception outcome, Context ctx) => 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, ctx) => onRetry(outcome, i)); } /// @@ -153,6 +168,28 @@ public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action< ), policyBuilder.ExceptionPredicates); } + /// + /// 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((action, context, cancellationToken) => RetryEngine.Implementation( + (ctx, ct) => { action(ctx, ct); return EmptyStruct.Instance; }, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + PredicateHelper.EmptyResultPredicates, + () => new RetryStateRetryForeverWithCount((outcome, i, ctx) => onRetry(outcome.Exception, i, ctx), context) + ), policyBuilder.ExceptionPredicates); + } + /// /// Builds a that will wait and retry times. /// On each retry, the duration to wait is calculated by calling with @@ -510,6 +547,27 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, ); } + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception and retry count. + /// + /// 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, exception, context) => sleepDurationProvider(retryCount), + (exception, i, timespan, context) => 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 @@ -530,6 +588,26 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, ); } + /// + /// 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. + /// + /// 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, outcome, 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 @@ -557,5 +635,33 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, (outcome, timespan, ctx) => onRetry(outcome.Exception, timespan, ctx), context) ), policyBuilder.ExceptionPredicates); } + + /// + /// 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. + /// + /// 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((action, context, cancellationToken) => RetryEngine.Implementation( + (ctx, ct) => { action(ctx, ct); return EmptyStruct.Instance; }, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + PredicateHelper.EmptyResultPredicates, + () => new RetryStateWaitAndRetryForeverWithCount( + (i, outcome, ctx) => sleepDurationProvider(i, outcome.Exception, ctx), + (outcome, i, timespan, ctx) => onRetry(outcome.Exception, i, timespan, ctx), context) + ), policyBuilder.ExceptionPredicates); + } } } \ No newline at end of file diff --git a/src/Polly.Shared/Retry/RetrySyntaxAsync.cs b/src/Polly.Shared/Retry/RetrySyntaxAsync.cs index 21f9459ee1d..d91a52ba9e8 100644 --- a/src/Polly.Shared/Retry/RetrySyntaxAsync.cs +++ b/src/Polly.Shared/Retry/RetrySyntaxAsync.cs @@ -169,7 +169,7 @@ public static RetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retry (action, context, cancellationToken, continueOnCapturedContext) => RetryEngine.ImplementationAsync( async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, + context, cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, @@ -204,7 +204,26 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Ac return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, ctx) => onRetry(outcome) + onRetryAsync: async (Exception outcome, Context ctx) => onRetry(outcome) +#pragma warning restore 1998 + ); + } + + /// + /// 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 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, context) => onRetry(outcome, i) #pragma warning restore 1998 ); } @@ -221,7 +240,22 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Fu { if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, ctx) => onRetryAsync(outcome)); + return policyBuilder.RetryForeverAsync(onRetryAsync: (Exception outcome, Context ctx) => onRetryAsync(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 asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, context) => onRetryAsync(outcome, i)); } /// @@ -243,6 +277,25 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Ac ); } + /// + /// 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 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) +#pragma warning restore 1998 + ); + } + /// /// Builds a that will retry indefinitely /// calling on each retry with the raised exception and context data. @@ -259,7 +312,7 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Fu (action, context, cancellationToken, continueOnCapturedContext) => RetryEngine.ImplementationAsync( async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, + context, cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, @@ -268,6 +321,31 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Fu ), policyBuilder.ExceptionPredicates); } + /// + /// 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 asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new RetryPolicy( + (action, context, cancellationToken, continueOnCapturedContext) => + RetryEngine.ImplementationAsync( + async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + PredicateHelper.EmptyResultPredicates, + () => new RetryStateRetryForeverWithCount((outcome, i, ctx) => onRetryAsync(outcome.Exception, i, ctx), context), + continueOnCapturedContext + ), policyBuilder.ExceptionPredicates); + } + /// /// Builds a that will wait and retry times. /// On each retry, the duration to wait is calculated by calling with @@ -465,7 +543,7 @@ public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, in (action, context, cancellationToken, continueOnCapturedContext) => RetryEngine.ImplementationAsync( async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, + context, cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, @@ -805,7 +883,7 @@ public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IE (action, context, cancellationToken, continueOnCapturedContext) => RetryEngine.ImplementationAsync( async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, + context, cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, @@ -868,6 +946,27 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil ); } + /// + /// Builds a that will wait and retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// 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 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, context) => sleepDurationProvider(retryCount), + (exception, i, timespan, context) => onRetry(exception, i, timespan) + ); + } + /// /// Builds a that will wait and retry indefinitely /// calling on each retry with the raised exception. @@ -889,6 +988,27 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil ); } + /// + /// Builds a that will wait and retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// 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 RetryPolicy 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, context) => sleepDurationProvider(retryCount), + (exception, i, timespan, context) => onRetryAsync(exception, i, timespan) + ); + } + /// /// Builds a that will wait and retry indefinitely /// calling on each retry with the raised exception and @@ -913,6 +1033,30 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil ); } + /// + /// Builds a that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count 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 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) +#pragma warning restore 1998 + ); + } + /// /// Builds a that will wait and retry indefinitely /// calling on each retry with the raised exception and @@ -933,6 +1077,26 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil ); } + /// + /// Builds a that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count 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 RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, outcome, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + /// /// Builds a that will wait and retry indefinitely /// calling on each retry with the raised exception and @@ -953,13 +1117,45 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil (action, context, cancellationToken, continueOnCapturedContext) => RetryEngine.ImplementationAsync( async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, + context, cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, () => new RetryStateWaitAndRetryForever( (i, outcome, ctx) => sleepDurationProvider(i, outcome.Exception, ctx), - (outcome, timespan, ctx) => onRetryAsync(outcome.Exception, timespan, ctx), + (outcome, timespan, ctx) => onRetryAsync(outcome.Exception, timespan, ctx), + context), + continueOnCapturedContext + ), policyBuilder.ExceptionPredicates); + } + + /// + /// Builds a that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count 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 RetryPolicy 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 RetryPolicy( + (action, context, cancellationToken, continueOnCapturedContext) => + RetryEngine.ImplementationAsync( + async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + PredicateHelper.EmptyResultPredicates, + () => new RetryStateWaitAndRetryForeverWithCount( + (i, outcome, ctx) => sleepDurationProvider(i, outcome.Exception, ctx), + (outcome, i, timespan, ctx) => onRetryAsync(outcome.Exception, i, timespan, ctx), context), continueOnCapturedContext ), policyBuilder.ExceptionPredicates); diff --git a/src/Polly.Shared/Retry/RetryTResultSyntax.cs b/src/Polly.Shared/Retry/RetryTResultSyntax.cs index 1ade32df69f..c54caf98586 100644 --- a/src/Polly.Shared/Retry/RetryTResultSyntax.cs +++ b/src/Polly.Shared/Retry/RetryTResultSyntax.cs @@ -129,7 +129,22 @@ public static RetryPolicy RetryForever(this PolicyBuilder onRetry(outcome)); + return policyBuilder.RetryForever((DelegateResult outcome, Context ctx) => 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, context) => onRetry(outcome, i)); } /// @@ -157,6 +172,31 @@ public static RetryPolicy RetryForever(this PolicyBuilder + /// 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( + (action, context, cancellationToken) => RetryEngine.Implementation( + action, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates, + () => new RetryStateRetryForeverWithCount(onRetry, context) + ), + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates); + } + /// /// Builds a that will wait and retry times. /// On each retry, the duration to wait is calculated by calling with @@ -560,6 +600,27 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuild ); } + /// + /// 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. + /// + /// 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, outcome, context) => sleepDurationProvider(retryCount), + (outcome, i, timespan, context) => 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 @@ -580,6 +641,26 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuild ); } + /// + /// 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. + /// + /// 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, outcome, 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 @@ -609,5 +690,35 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuild policyBuilder.ResultPredicates ); } + + /// + /// 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. + /// + /// 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( + (action, context, cancellationToken) => RetryEngine.Implementation( + action, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates, + () => new RetryStateWaitAndRetryForeverWithCount(sleepDurationProvider, onRetry, context) + ), + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates + ); + } } } \ No newline at end of file diff --git a/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs b/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs index 8b826288dfe..63c15113b58 100644 --- a/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs +++ b/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs @@ -205,7 +205,26 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, ctx) => onRetry(outcome) + onRetryAsync: async (DelegateResult outcome, Context ctx) => onRetry(outcome) +#pragma warning restore 1998 + ); + } + + /// + /// 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 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, context) => onRetry(outcome, i) #pragma warning restore 1998 ); } @@ -222,7 +241,22 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder { if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, ctx) => onRetryAsync(outcome)); + return policyBuilder.RetryForeverAsync(onRetryAsync: (DelegateResult outcome, Context ctx) => onRetryAsync(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 asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, context) => onRetryAsync(outcome, i)); } /// @@ -244,6 +278,25 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder ); } + /// + /// 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 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) +#pragma warning restore 1998 + ); + } + /// /// Builds a that will retry indefinitely /// calling on each retry with the handled exception or result and context data. @@ -271,6 +324,33 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder.ResultPredicates); } + /// + /// 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 asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new RetryPolicy( + (action, context, cancellationToken, continueOnCapturedContext) => + RetryEngine.ImplementationAsync( + action, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates, + () => new RetryStateRetryForeverWithCount(onRetryAsync, context), + continueOnCapturedContext + ), + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates); + } + /// /// Builds a that will wait and retry times. /// On each retry, the duration to wait is calculated by calling with @@ -869,6 +949,27 @@ public static RetryPolicy WaitAndRetryForeverAsync(this Policy ); } + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result. + /// + /// 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 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, context) => sleepDurationProvider(retryCount), + (outcome, i, timespan, context) => 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. @@ -890,6 +991,27 @@ public static RetryPolicy WaitAndRetryForeverAsync(this Policy ); } + /// + /// 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. + /// + /// 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 RetryPolicy 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, context) => sleepDurationProvider(retryCount), + (outcome, i, timespan, context) => onRetryAsync(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 @@ -913,6 +1035,29 @@ public static RetryPolicy WaitAndRetryForeverAsync(this Policy ); } + /// + /// 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. + /// + /// 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 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) +#pragma warning restore 1998 + ); + } + /// /// Builds a that will wait and retry indefinitely until the action succeeds, /// calling on each retry with the handled exception or result and @@ -933,6 +1078,26 @@ public static RetryPolicy WaitAndRetryForeverAsync(this Policy ); } + /// + /// 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. + /// + /// 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 RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, outcome, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + /// /// Builds a that will wait and retry indefinitely until the action succeeds, /// calling on each retry with the handled exception or result and @@ -963,6 +1128,37 @@ public static RetryPolicy WaitAndRetryForeverAsync(this Policy policyBuilder.ExceptionPredicates, policyBuilder.ResultPredicates); } + + /// + /// 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. + /// + /// 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 RetryPolicy 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 RetryPolicy( + (action, context, cancellationToken, continueOnCapturedContext) => + RetryEngine.ImplementationAsync( + action, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates, + () => new RetryStateWaitAndRetryForeverWithCount(sleepDurationProvider, onRetryAsync, context), + continueOnCapturedContext + ), + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates); + } } } diff --git a/src/Polly.Shared/Timeout/TimeoutEngine.cs b/src/Polly.Shared/Timeout/TimeoutEngine.cs index cf81cae7a8c..2c331523188 100644 --- a/src/Polly.Shared/Timeout/TimeoutEngine.cs +++ b/src/Polly.Shared/Timeout/TimeoutEngine.cs @@ -18,7 +18,7 @@ internal static TResult Implementation( CancellationToken cancellationToken, Func timeoutProvider, TimeoutStrategy timeoutStrategy, - Action onTimeout) + Action onTimeout) { cancellationToken.ThrowIfCancellationRequested(); TimeSpan timeout = timeoutProvider(context); @@ -68,7 +68,7 @@ internal static TResult Implementation( { if (timeoutCancellationTokenSource.IsCancellationRequested) { - onTimeout(context, timeout, actionTask); + onTimeout(context, timeout, actionTask, ex); throw new TimeoutRejectedException("The delegate executed through TimeoutPolicy did not complete within the timeout.", ex); } diff --git a/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs b/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs index 3d5b5614cc4..448b219e349 100644 --- a/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs +++ b/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs @@ -12,7 +12,7 @@ internal static async Task ImplementationAsync( Context context, Func timeoutProvider, TimeoutStrategy timeoutStrategy, - Func onTimeoutAsync, + Func onTimeoutAsync, CancellationToken cancellationToken, bool continueOnCapturedContext) { @@ -51,12 +51,12 @@ internal static async Task ImplementationAsync( .WhenAny(actionTask, timeoutTask).ConfigureAwait(continueOnCapturedContext)).ConfigureAwait(continueOnCapturedContext); } - catch (Exception e) + catch (Exception ex) { if (timeoutCancellationTokenSource.IsCancellationRequested) { - await onTimeoutAsync(context, timeout, actionTask).ConfigureAwait(continueOnCapturedContext); - throw new TimeoutRejectedException("The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.", e); + 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; diff --git a/src/Polly.Shared/Timeout/TimeoutSyntax.cs b/src/Polly.Shared/Timeout/TimeoutSyntax.cs index 9fb24bfe3b0..d4b1289ed60 100644 --- a/src/Polly.Shared/Timeout/TimeoutSyntax.cs +++ b/src/Polly.Shared/Timeout/TimeoutSyntax.cs @@ -16,7 +16,7 @@ public partial class Policy public static TimeoutPolicy Timeout(int seconds) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); } @@ -31,7 +31,7 @@ public static TimeoutPolicy Timeout(int seconds) public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); } @@ -52,6 +52,22 @@ public static TimeoutPolicy Timeout(int seconds, Action return Timeout(ctx => 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 co-operatively 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(ctx => 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. /// @@ -69,6 +85,23 @@ public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy return Timeout(ctx => 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 co-operatively 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(ctx => 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. /// @@ -78,7 +111,7 @@ public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy public static TimeoutPolicy Timeout(TimeSpan timeout) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeout, TimeoutStrategy.Optimistic, doNothing); } @@ -93,7 +126,7 @@ public static TimeoutPolicy Timeout(TimeSpan timeout) public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeout, timeoutStrategy, doNothing); } @@ -114,6 +147,22 @@ public static TimeoutPolicy Timeout(TimeSpan timeout, Action 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 co-operatively 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(ctx => 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. /// @@ -131,6 +180,23 @@ public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStr return Timeout(ctx => 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 co-operatively 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(ctx => 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. /// @@ -141,7 +207,7 @@ public static TimeoutPolicy Timeout(Func timeoutProvider) { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); } @@ -156,7 +222,7 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrat { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeoutProvider(), timeoutStrategy, doNothing); } @@ -176,6 +242,22 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, Action 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 co-operatively 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(ctx => 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. /// @@ -193,6 +275,23 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrat return Timeout(ctx => 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 co-operatively 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(ctx => 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. /// @@ -201,7 +300,7 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrat /// The policy instance. public static TimeoutPolicy Timeout(Func timeoutProvider) { - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); } @@ -214,7 +313,7 @@ public static TimeoutPolicy Timeout(Func timeoutProvider) /// timeoutProvider public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) { - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(timeoutProvider, timeoutStrategy, doNothing); } @@ -232,6 +331,20 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, Act 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + 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. /// @@ -243,6 +356,23 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, Act /// 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, ex) => 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 co-operatively 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)); diff --git a/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs b/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs index c47be90d93d..f14ebc2c684 100644 --- a/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs +++ b/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs @@ -16,7 +16,7 @@ public partial class Policy public static TimeoutPolicy TimeoutAsync(int seconds) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); } @@ -31,7 +31,7 @@ public static TimeoutPolicy TimeoutAsync(int seconds) public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); } @@ -54,6 +54,23 @@ public static TimeoutPolicy TimeoutAsync(int seconds, Func TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -71,6 +88,23 @@ public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStr return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively 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 TimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -80,7 +114,7 @@ public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStr public static TimeoutPolicy TimeoutAsync(TimeSpan timeout) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => timeout, TimeoutStrategy.Optimistic, doNothingAsync); } @@ -95,7 +129,7 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout) public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => timeout, timeoutStrategy, doNothingAsync); } @@ -116,6 +150,22 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Func timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(ctx => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -133,6 +183,23 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeo return TimeoutAsync(ctx => timeout, timeoutStrategy, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(ctx => timeout, timeoutStrategy, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -143,7 +210,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider) { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); } @@ -158,7 +225,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Timeout { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => timeoutProvider(), timeoutStrategy, doNothingAsync); } @@ -178,6 +245,22 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Func timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -195,6 +278,23 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Timeout return TimeoutAsync(ctx => timeoutProvider(), timeoutStrategy, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(ctx => timeoutProvider(), timeoutStrategy, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -203,7 +303,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Timeout /// The policy instance. public static TimeoutPolicy TimeoutAsync(Func timeoutProvider) { - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); } @@ -217,7 +317,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider /// timeoutProvider public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) { - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); } @@ -236,6 +336,20 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -247,6 +361,24 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider /// timeoutProvider /// onTimeoutAsync public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, ex) => onTimeoutAsync(ctx, timeout, task)); + } + + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy + , Func onTimeoutAsync) { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); diff --git a/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs b/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs index 9ef5bf8f464..f28c4430845 100644 --- a/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs +++ b/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs @@ -16,7 +16,7 @@ public partial class Policy public static TimeoutPolicy Timeout(int seconds) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); } @@ -32,7 +32,7 @@ public static TimeoutPolicy Timeout(int seconds) public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); } @@ -52,6 +52,21 @@ public static TimeoutPolicy Timeout(int seconds, Action(ctx => 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 co-operatively 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(ctx => 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. /// @@ -70,6 +85,24 @@ public static TimeoutPolicy Timeout(int seconds, TimeoutStrate return Timeout(ctx => 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 co-operatively 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(ctx => 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. /// @@ -79,7 +112,7 @@ public static TimeoutPolicy Timeout(int seconds, TimeoutStrate public static TimeoutPolicy Timeout(TimeSpan timeout) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeout, TimeoutStrategy.Optimistic, doNothing); } @@ -95,7 +128,7 @@ public static TimeoutPolicy Timeout(TimeSpan timeout) public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeout, timeoutStrategy, doNothing); } @@ -115,6 +148,21 @@ public static TimeoutPolicy Timeout(TimeSpan timeout, Action(ctx => 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 co-operatively 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(ctx => 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. /// @@ -132,6 +180,23 @@ public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutS return Timeout(ctx => 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 co-operatively 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(ctx => 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. /// @@ -142,7 +207,7 @@ public static TimeoutPolicy Timeout(Func timeoutProv { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); } @@ -158,7 +223,7 @@ public static TimeoutPolicy Timeout(Func timeoutProv { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeoutProvider(), timeoutStrategy, doNothing); } @@ -178,6 +243,22 @@ public static TimeoutPolicy Timeout(Func timeoutProv return Timeout(ctx => 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 co-operatively 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(ctx => 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. /// @@ -196,6 +277,24 @@ public static TimeoutPolicy Timeout(Func timeoutProv return Timeout(ctx => 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 co-operatively 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(ctx => 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. /// @@ -204,7 +303,7 @@ public static TimeoutPolicy Timeout(Func timeoutProv /// The policy instance. public static TimeoutPolicy Timeout(Func timeoutProvider) { - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); } @@ -218,7 +317,7 @@ public static TimeoutPolicy Timeout(Func ti /// timeoutProvider public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) { - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(timeoutProvider, timeoutStrategy, doNothing); } @@ -236,6 +335,20 @@ public static TimeoutPolicy Timeout(Func ti 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + 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. /// @@ -248,6 +361,24 @@ public static TimeoutPolicy Timeout(Func ti /// 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, ex) => 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 co-operatively 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)); diff --git a/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs b/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs index f2b58a92c92..7791b0cfb2f 100644 --- a/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs +++ b/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs @@ -17,7 +17,7 @@ public static TimeoutPolicy TimeoutAsync(int seconds) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); } @@ -32,7 +32,7 @@ public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutS { TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); } @@ -52,6 +52,22 @@ public static TimeoutPolicy TimeoutAsync(int seconds, Func(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -69,6 +85,23 @@ public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutS return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -79,7 +112,7 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => timeout, TimeoutStrategy.Optimistic, doNothingAsync); } @@ -94,7 +127,7 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Tim { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => timeout, timeoutStrategy, doNothingAsync); } @@ -115,6 +148,23 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Fun return TimeoutAsync(ctx => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(ctx => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -132,6 +182,23 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Tim return TimeoutAsync(ctx => timeout, timeoutStrategy, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); + + return TimeoutAsync(ctx => timeout, timeoutStrategy, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -142,7 +209,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeou { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); } @@ -157,7 +224,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeou { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => timeoutProvider(), timeoutStrategy, doNothingAsync); } @@ -177,6 +244,22 @@ public static TimeoutPolicy TimeoutAsync(Func timeou return TimeoutAsync(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -191,6 +274,23 @@ public static TimeoutPolicy TimeoutAsync(Func timeou { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + return TimeoutAsync(ctx => timeoutProvider(), onTimeoutAsync); + } + + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + return TimeoutAsync(ctx => timeoutProvider(), timeoutStrategy, onTimeoutAsync); } @@ -202,7 +302,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeou /// The policy instance. public static TimeoutPolicy TimeoutAsync(Func timeoutProvider) { - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); } @@ -215,7 +315,7 @@ public static TimeoutPolicy TimeoutAsync(FuncThe policy instance. public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) { - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); } @@ -233,6 +333,20 @@ public static TimeoutPolicy TimeoutAsync(Func(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a 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. /// @@ -244,6 +358,23 @@ public static TimeoutPolicy TimeoutAsync(FunctimeoutProvider /// onTimeoutAsync public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, ex) => onTimeoutAsync(ctx, timeout, task)); + } + + /// + /// Builds a 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 co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); @@ -255,7 +386,7 @@ public static TimeoutPolicy TimeoutAsync(Func with context from the executing . /// /// The execution . - internal override void SetPolicyContext(Context executionContext) + /// 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; - base.SetPolicyContext(executionContext); + base.SetPolicyContext(executionContext, out _, out _); } } @@ -24,11 +29,16 @@ public partial class PolicyWrap /// Updates the execution with context from the executing . /// /// The execution . - internal override void SetPolicyContext(Context executionContext) + /// 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; - base.SetPolicyContext(executionContext); + base.SetPolicyContext(executionContext, out _, out _); } } } diff --git a/src/Polly.SharedSpecs/Caching/CacheAsyncSpecs.cs b/src/Polly.SharedSpecs/Caching/CacheAsyncSpecs.cs index 07a27318512..bdaa49d2419 100644 --- a/src/Polly.SharedSpecs/Caching/CacheAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/CacheAsyncSpecs.cs @@ -42,6 +42,18 @@ public void Should_throw_when_cache_key_strategy_is_null() action.ShouldThrow().And.ParamName.Should().Be("cacheKeyStrategy"); } + [Fact] + public void Should_throw_informative_exception_when_sync_execute_on_an_async_policy() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + + var cachePolicy = Policy.CacheAsync(cacheProvider, TimeSpan.FromMinutes(5)); + + Action action = () => cachePolicy.Execute(() => 0); + + action.ShouldThrow(); + } + #endregion #region Caching behaviours diff --git a/src/Polly.SharedSpecs/Caching/CacheSpecs.cs b/src/Polly.SharedSpecs/Caching/CacheSpecs.cs index e0f0a986404..83878b0f4ab 100644 --- a/src/Polly.SharedSpecs/Caching/CacheSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/CacheSpecs.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Polly.Caching; using Polly.Specs.Helpers; @@ -41,6 +42,17 @@ public void Should_throw_when_cache_key_strategy_is_null() action.ShouldThrow().And.ParamName.Should().Be("cacheKeyStrategy"); } + [Fact] + public void Should_throw_informative_exception_when_async_execute_on_a_sync_policy() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + + var cachePolicy = Policy.Cache(cacheProvider, TimeSpan.FromMinutes(5)); + + cachePolicy.Awaiting(p => p.ExecuteAsync(() => Task.FromResult(0))) + .ShouldThrow(); + } + #endregion #region Caching behaviours diff --git a/src/Polly.SharedSpecs/Caching/CacheTResultAsyncSpecs.cs b/src/Polly.SharedSpecs/Caching/CacheTResultAsyncSpecs.cs index 733f8fab70b..c50e3cf9fc5 100644 --- a/src/Polly.SharedSpecs/Caching/CacheTResultAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/CacheTResultAsyncSpecs.cs @@ -42,6 +42,17 @@ public void Should_throw_when_cache_key_strategy_is_null() action.ShouldThrow().And.ParamName.Should().Be("cacheKeyStrategy"); } + [Fact] + public void Should_throw_informative_exception_when_sync_execute_on_an_async_policy() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + + var cachePolicy = Policy.CacheAsync(cacheProvider, TimeSpan.FromMinutes(5)); + + Action action = () => cachePolicy.Execute(() => 0); + + action.ShouldThrow(); + } #endregion #region Caching behaviours diff --git a/src/Polly.SharedSpecs/Caching/CacheTResultSpecs.cs b/src/Polly.SharedSpecs/Caching/CacheTResultSpecs.cs index 50a71186161..917bb2708c0 100644 --- a/src/Polly.SharedSpecs/Caching/CacheTResultSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/CacheTResultSpecs.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Polly.Caching; using Polly.Specs.Helpers; @@ -40,6 +41,17 @@ public void Should_throw_when_cache_key_strategy_is_null() action.ShouldThrow().And.ParamName.Should().Be("cacheKeyStrategy"); } + [Fact] + public void Should_throw_informative_exception_when_async_execute_on_a_sync_policy() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + + var cachePolicy = Policy.Cache(cacheProvider, TimeSpan.FromMinutes(5)); + + cachePolicy.Awaiting(p => p.ExecuteAsync(() => Task.FromResult(0))) + .ShouldThrow(); + } + #endregion #region Caching behaviours diff --git a/src/Polly.SharedSpecs/Caching/GenericCacheProviderAsyncSpecs.cs b/src/Polly.SharedSpecs/Caching/GenericCacheProviderAsyncSpecs.cs new file mode 100644 index 00000000000..fa5219f9a3c --- /dev/null +++ b/src/Polly.SharedSpecs/Caching/GenericCacheProviderAsyncSpecs.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Polly.Caching; +using Polly.Specs.Helpers; +using Polly.Specs.Helpers.Caching; +using Polly.Utilities; +using Polly.Wrap; +using Xunit; + +namespace Polly.Specs.Caching +{ + [Collection(Polly.Specs.Helpers.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() + { + const string operationKey = "SomeOperationKey"; + + bool onErrorCalled = false; + Action onError = (ctx, key, exc) => { onErrorCalled = true; }; + + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); + + (await stubCacheProvider.GetAsync(operationKey, CancellationToken.None, false)).Should().BeNull(); + ResultPrimitive result = await cache.ExecuteAsync(async ctx => + { + await TaskHelper.EmptyTask.ConfigureAwait(false); + return ResultPrimitive.Substitute; + }, new Context(operationKey)); + + 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"; + + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + + (await stubCacheProvider.GetAsync(operationKey, CancellationToken.None, false)).Should().BeNull(); + + (await cache.ExecuteAsync(async ctx => + { + await TaskHelper.EmptyTask.ConfigureAwait(false); + return ResultPrimitive.Substitute; + }, new Context(operationKey))).Should().Be(valueToReturn); + + (await stubCacheProvider.GetAsync(operationKey, CancellationToken.None, false)).Should().Be(valueToReturn); + } + + public void Dispose() + { + SystemClock.Reset(); + } + } +} diff --git a/src/Polly.SharedSpecs/Caching/GenericCacheProviderSpecs.cs b/src/Polly.SharedSpecs/Caching/GenericCacheProviderSpecs.cs new file mode 100644 index 00000000000..ab6d6193e7d --- /dev/null +++ b/src/Polly.SharedSpecs/Caching/GenericCacheProviderSpecs.cs @@ -0,0 +1,55 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Polly.Caching; +using Polly.Specs.Helpers; +using Polly.Specs.Helpers.Caching; +using Polly.Utilities; +using Polly.Wrap; +using Xunit; + +namespace Polly.Specs.Caching +{ + [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] + public class GenericCacheProviderSpecs : IDisposable + { + [Fact] + public void Should_not_error_for_executions_on_non_nullable_types_if_cache_does_not_hold_value() + { + const string operationKey = "SomeOperationKey"; + + bool onErrorCalled = false; + Action onError = (ctx, key, exc) => { onErrorCalled = true; }; + + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); + + stubCacheProvider.Get(operationKey).Should().BeNull(); + ResultPrimitive result = cache.Execute(ctx => ResultPrimitive.Substitute, new Context(operationKey)); + + 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"; + + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + + stubCacheProvider.Get(operationKey).Should().BeNull(); + + cache.Execute(ctx => { return valueToReturn; }, new Context(operationKey)).Should().Be(valueToReturn); + + stubCacheProvider.Get(operationKey).Should().Be(valueToReturn); + } + + public void Dispose() + { + SystemClock.Reset(); + } + } +} diff --git a/src/Polly.SharedSpecs/Caching/SerializingCacheProviderAsyncSpecs.cs b/src/Polly.SharedSpecs/Caching/SerializingCacheProviderAsyncSpecs.cs index d8e27b9d337..ac2c75a605e 100644 --- a/src/Polly.SharedSpecs/Caching/SerializingCacheProviderAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/SerializingCacheProviderAsyncSpecs.cs @@ -65,6 +65,25 @@ public async Task Single_generic_SerializingCacheProvider_should_serialize_on_pu .Which.Original.Should().Be(objectToCache); } + [Fact] + public async Task Single_generic_SerializingCacheProvider_should_not_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(object); + string key = "some key"; + + SerializingCacheProviderAsync serializingCacheProvider = new SerializingCacheProviderAsync(stubCacheProvider.AsyncFor(), stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public async Task Single_generic_SerializingCacheProvider_should_deserialize_on_get() { @@ -87,6 +106,26 @@ public async Task Single_generic_SerializingCacheProvider_should_deserialize_on_ 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.Get(key).Should().BeNull(); + + SerializingCacheProviderAsync serializingCacheProvider = new SerializingCacheProviderAsync(stubCacheProvider.AsyncFor(), stubSerializer); + object fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(object)); + } + [Fact] public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { @@ -107,6 +146,25 @@ public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_ .Which.Original.Should().Be(objectToCache); } + [Fact] + public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_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(object); + string key = "some key"; + + SerializingCacheProviderAsync serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { @@ -128,6 +186,26 @@ public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_ 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.Get(key).Should().BeNull(); + + SerializingCacheProviderAsync serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + object fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(object)); + } + #endregion #region TResult-to-TSerialized serializer @@ -184,6 +262,25 @@ public async Task Double_generic_SerializingCacheProvider_should_serialize_on_pu .Which.Original.Should().Be(objectToCache); } + [Fact] + public async Task Double_generic_SerializingCacheProvider_should_not_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(ResultPrimitive); + string key = "some key"; + + SerializingCacheProviderAsync> serializingCacheProvider = new SerializingCacheProviderAsync>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public async Task Double_generic_SerializingCacheProvider_should_deserialize_on_get() { @@ -205,6 +302,26 @@ public async Task Double_generic_SerializingCacheProvider_should_deserialize_on_ 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.Get(key).Should().BeNull(); + + SerializingCacheProviderAsync> serializingCacheProvider = new SerializingCacheProviderAsync>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + ResultPrimitive fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(ResultPrimitive)); + } + [Fact] public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { @@ -226,6 +343,26 @@ public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_ .Which.Original.Should().Be(objectToCache); } + [Fact] + public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_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(ResultPrimitive); + string key = "some key"; + + SerializingCacheProviderAsync> serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { @@ -242,12 +379,33 @@ public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_ stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - object fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); + ResultPrimitive fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); deserializeInvoked.Should().Be(true); 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.Get(key).Should().BeNull(); + + SerializingCacheProviderAsync> serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + ResultPrimitive fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(ResultPrimitive)); + } + #endregion } } \ No newline at end of file diff --git a/src/Polly.SharedSpecs/Caching/SerializingCacheProviderSpecs.cs b/src/Polly.SharedSpecs/Caching/SerializingCacheProviderSpecs.cs index a77b189c4f9..d8c4c2c191f 100644 --- a/src/Polly.SharedSpecs/Caching/SerializingCacheProviderSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/SerializingCacheProviderSpecs.cs @@ -63,6 +63,25 @@ public void Single_generic_SerializingCacheProvider_should_serialize_on_put() .Which.Original.Should().Be(objectToCache); } + [Fact] + public void Single_generic_SerializingCacheProvider_should_not_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(object); + string key = "some key"; + + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public void Single_generic_SerializingCacheProvider_should_deserialize_on_get() { @@ -85,6 +104,26 @@ public void Single_generic_SerializingCacheProvider_should_deserialize_on_get() 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.Get(key).Should().BeNull(); + + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + object fromCache = serializingCacheProvider.Get(key); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(object)); + } + [Fact] public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { @@ -105,6 +144,25 @@ public void Single_generic_SerializingCacheProvider_from_extension_syntax_should .Which.Original.Should().Be(objectToCache); } + [Fact] + public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_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(object); + string key = "some key"; + + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { @@ -126,6 +184,26 @@ public void Single_generic_SerializingCacheProvider_from_extension_syntax_should 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.Get(key).Should().BeNull(); + + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + object fromCache = serializingCacheProvider.Get(key); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(object)); + } + #endregion #region TResult-to-TSerialized serializer @@ -182,6 +260,25 @@ public void Double_generic_SerializingCacheProvider_should_serialize_on_put() .Which.Original.Should().Be(objectToCache); } + [Fact] + public void Double_generic_SerializingCacheProvider_should_not_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(ResultPrimitive); + string key = "some key"; + + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public void Double_generic_SerializingCacheProvider_should_deserialize_on_get() { @@ -203,6 +300,26 @@ public void Double_generic_SerializingCacheProvider_should_deserialize_on_get() 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.Get(key).Should().BeNull(); + + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + ResultPrimitive fromCache = serializingCacheProvider.Get(key); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(ResultPrimitive)); + } + [Fact] public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { @@ -224,6 +341,27 @@ public void Double_generic_SerializingCacheProvider_from_extension_syntax_should .Which.Original.Should().Be(objectToCache); } + [Fact] + public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_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(ResultPrimitive); + string key = "some key"; + + SerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { @@ -246,6 +384,27 @@ public void Double_generic_SerializingCacheProvider_from_extension_syntax_should 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.Get(key).Should().BeNull(); + + SerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + ResultPrimitive fromCache = serializingCacheProvider.Get(key); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(ResultPrimitive)); + } + #endregion } } diff --git a/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems b/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems index 57226a4ae79..76534ecf69f 100644 --- a/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems +++ b/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems @@ -24,6 +24,8 @@ + + diff --git a/src/Polly.SharedSpecs/Retry/RetryForeverAsyncSpecs.cs b/src/Polly.SharedSpecs/Retry/RetryForeverAsyncSpecs.cs index 47484b79f97..290c1d379af 100644 --- a/src/Polly.SharedSpecs/Retry/RetryForeverAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Retry/RetryForeverAsyncSpecs.cs @@ -143,6 +143,22 @@ public void Should_call_onretry_on_each_retry_with_the_passed_context() .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() + .RetryForeverAsync((_, retryCount) => retryCounts.Add(retryCount)); + + policy.RaiseExceptionAsync(3); + + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } + [Fact] public void Context_should_be_empty_if_execute_not_called_with_any_data() { diff --git a/src/Polly.SharedSpecs/Retry/RetryForeverSpecs.cs b/src/Polly.SharedSpecs/Retry/RetryForeverSpecs.cs index 30669700df2..8dead2a7f07 100644 --- a/src/Polly.SharedSpecs/Retry/RetryForeverSpecs.cs +++ b/src/Polly.SharedSpecs/Retry/RetryForeverSpecs.cs @@ -197,6 +197,22 @@ public void Should_call_onretry_on_each_retry_with_the_passed_context() .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() { diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs index cab1fcfeb3c..064d4144d55 100644 --- a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs @@ -218,6 +218,23 @@ public async Task Should_call_onretry_on_each_retry_with_the_current_exception() .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 = i => TimeSpan.Zero; + + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider, (_, retryCount, __) => retryCounts.Add(retryCount)); + + policy.RaiseExceptionAsync(3); + + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } + [Fact] public void Should_not_call_onretry_when_no_retries_are_performed() { diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs index 0fbe435b8e6..a7a72a76552 100644 --- a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs @@ -216,6 +216,23 @@ public void Should_call_onretry_on_each_retry_with_the_current_exception() .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 = i => TimeSpan.Zero; + + var policy = Policy + .Handle() + .WaitAndRetryForever(provider, (_, retryCount, __) => retryCounts.Add(retryCount)); + + policy.RaiseException(3); + + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } + [Fact] public void Should_not_call_onretry_when_no_retries_are_performed() { diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs index 7b6bd23d892..31af90fc622 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs @@ -120,7 +120,18 @@ public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrate [Fact] public void Should_throw_when_onTimeout_is_null_with_timespan() { - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), null); + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -129,7 +140,18 @@ public void Should_throw_when_onTimeout_is_null_with_timespan() [Fact] public void Should_throw_when_onTimeout_is_null_with_seconds() { - Action policy = () => Policy.TimeoutAsync(30, null); + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -147,7 +169,18 @@ public void Should_throw_when_timeoutProvider_is_null() [Fact] public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() { - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), null); + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeout); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -560,9 +593,33 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); } + + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - #endregion + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + + })) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + + #endregion #region onTimeout overload - optimistic @@ -705,8 +762,34 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o taskPassedToOnTimeout.Should().BeNull(); } - + + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + + }, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + #endregion } -} \ No newline at end of file +} diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs index e606084faed..b0f366fdd40 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs @@ -107,6 +107,15 @@ public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() policy.ShouldNotThrow(); } + [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.ShouldNotThrow(); + } + [Fact] public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() { @@ -116,10 +125,30 @@ public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrate policy.ShouldNotThrow(); } + [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.ShouldNotThrow(); + } + [Fact] public void Should_throw_when_onTimeout_is_null_with_timespan() { - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -128,7 +157,18 @@ public void Should_throw_when_onTimeout_is_null_with_timespan() [Fact] public void Should_throw_when_onTimeout_is_null_with_seconds() { - Action policy = () => Policy.Timeout(30, null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -137,7 +177,7 @@ public void Should_throw_when_onTimeout_is_null_with_seconds() [Fact] public void Should_throw_when_timeoutProvider_is_null() { - Action policy = () => Policy.Timeout((Func) null); + Action policy = () => Policy.Timeout((Func)null); policy.ShouldThrow() .And.ParamName.Should().Be("timeoutProvider"); @@ -146,7 +186,18 @@ public void Should_throw_when_timeoutProvider_is_null() [Fact] public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() { - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -302,7 +353,7 @@ public void Should_throw_when_timeout_is_less_than_execution_duration__optimisti policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) // Delegate observes cancellation token, so permitting optimistic cancellation. .ShouldThrow(); } - + [Fact] public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() { @@ -353,13 +404,14 @@ public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token 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), + SystemClock.Sleep(TimeSpan.FromSeconds(timeout * 2), CancellationToken.None // ... but if the executed delegate does not observe it ); - } - , userTokenSource.Token) + } + , userTokenSource.Token) ).ShouldThrow(); // ... it's still the timeout we expect. } } @@ -465,13 +517,13 @@ 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) { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (ctx, span, task) => { timeoutPassedToOnTimeout = span; }; var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); - + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) .ShouldThrow(); @@ -484,7 +536,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu [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"]; + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (ctx, span, task) => { timeoutPassedToOnTimeout = span; }; @@ -546,6 +598,23 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_c } + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { exceptionPassedToOnTimeout = exception; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + #endregion #region onTimeout overload - optimistic @@ -594,7 +663,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) { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (ctx, span, task) => { timeoutPassedToOnTimeout = span; }; @@ -614,7 +683,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu [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"]; + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (ctx, span, task) => { timeoutPassedToOnTimeout = span; }; @@ -625,7 +694,7 @@ 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) + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; policy.Invoking(p => p.Execute((ctx, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), context, userCancellationToken)) @@ -649,9 +718,25 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o taskPassedToOnTimeout.Should().BeNull(); } - - #endregion + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, 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), userCancellationToken)) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs index 4eddf7ac13f..4fc4dd5c643 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs @@ -120,7 +120,18 @@ public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrate [Fact] public void Should_throw_when_onTimeout_is_null_with_timespan() { - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), null); + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -129,7 +140,19 @@ public void Should_throw_when_onTimeout_is_null_with_timespan() [Fact] public void Should_throw_when_onTimeout_is_null_with_seconds() { - Action policy = () => Policy.TimeoutAsync(30, null); + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -147,7 +170,18 @@ public void Should_throw_when_timeoutProvider_is_null() [Fact] public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() { - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), null); + Func onTimeoutAsync = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeoutAsync); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -561,6 +595,31 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo } + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + #endregion #region onTimeout overload - optimistic @@ -706,8 +765,33 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o taskPassedToOnTimeout.Should().BeNull(); } + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } #endregion } -} \ No newline at end of file +} diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs index ab1f574a74b..9b761ad3c05 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs @@ -120,7 +120,18 @@ public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrate [Fact] public void Should_throw_when_onTimeout_is_null_with_timespan() { - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -129,7 +140,18 @@ public void Should_throw_when_onTimeout_is_null_with_timespan() [Fact] public void Should_throw_when_onTimeout_is_null_with_seconds() { - Action policy = () => Policy.Timeout(30, null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -147,7 +169,18 @@ public void Should_throw_when_timeoutProvider_is_null() [Fact] public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() { - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + + policy.ShouldThrow() + .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); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -609,6 +642,27 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_c exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); } + + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { exceptionPassedToOnTimeout = exception; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } #endregion @@ -734,7 +788,29 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o taskPassedToOnTimeout.Should().BeNull(); } + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, 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)) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + #endregion } -} \ No newline at end of file +} diff --git a/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecs.cs b/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecs.cs index e84e87470f2..db910ec4ce2 100644 --- a/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecs.cs +++ b/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecs.cs @@ -59,6 +59,33 @@ public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_Policy 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: (result, retryCount, 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() { @@ -189,6 +216,33 @@ public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_Policy 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: (result, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + }) + .WithPolicyKey("FallbackPolicy"); + + ISyncPolicy retry = Policy + .Handle() + .Retry(1, onRetry: (result, retryCount, 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() { diff --git a/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs b/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs index cd483141ecb..c203da0cd4d 100644 --- a/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs +++ b/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs @@ -61,6 +61,34 @@ public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_ policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); } + [Fact] + public void 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: (result, retryCount, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + policyWrap.ExecuteAsync(() => throw new Exception()); + } + [Fact] public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() { @@ -192,6 +220,34 @@ public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_ policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); } + [Fact] + public void 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: (result, retryCount, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + policyWrap.ExecuteAsync(() => throw new Exception()); + } + [Fact] public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() { diff --git a/src/Polly.nuspec b/src/Polly.nuspec index 263bad60ebb..a078e9659a7 100644 --- a/src/Polly.nuspec +++ b/src/Polly.nuspec @@ -13,7 +13,15 @@ Exception Handling Resilience Transient Fault Policy Circuit Breaker CircuitBreaker Retry Wait Cache Cache-aside Bulkhead Fallback Timeout Throttle Parallelization Copyright © 2018, App vNext + 6.1.0 + --------------------- + - Bug Fix: Context.PolicyKey behaviour in PolicyWrap + - Bug Fix: CachePolicy behaviour with non-nullable types + - Enhancement: WaitAnd/RetryForever overloads where onRetry takes the retry number as a parameter + - Enhancement: Overloads where onTimeout takes thrown exception as a parameter + 6.0.1 + --------------------- - Version 6 RTM, for integration to ASPNET Core 2.1 IHttpClientFactory 6.0.0-v6alpha