diff --git a/README.md b/README.md index b089a36..772bd27 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,18 @@ -# Polly.Contrib.BlankTemplate +# Polly.Contrib.RequestHedging -Polly.Contrib.BlankTemplate is blank template to use a starting point to develop contributions to Polly.Contrib. +Polly.Contrib.RequestHedginge allow the sequential and delayed execution of a set of tasks, until one of them completes successfully or until all of them are completed. +First task that is successfully completed triggers the cancellation and disposal of all other tasks and/or adjacent allocated resources. -_If you are looking to develop a custom policy, start instead from the repo_ [Polly.Contrib.CustomPolicyTemplates](https://github.com/Polly-Contrib/Polly.Contrib.CustomPolicyTemplates). +# Installing via NuGet -## How to use the template + Install-Package Polly.Contrib.RequestHedging -This repo is essentially just a blank template of a solution that: +# Usage -+ references [Polly](https://github.com/App-vNext/Polly) -+ contains multi-targeting for the current compilation targets which Polly supports - - builds against Net Standard 1.1 and Net Standard 2.0 - - tests againt .Net Core 1.1, .Net core 2.0, .Net Framework 4.6.2, .Net Framework 4.7.2 -+ contains a build script which builds to a nuget package styled `Polly.Contrib.X` +``` C# +PolicyBuilder[] policyBuilder; -## Getting your contribution included in Polly.Contrib - -Reach out to the Polly team at our [slack channel](http://pollytalk.slack.com) or the main [Polly project Github](https://github.com/App-vNext/Polly). - -We can set up a repo in the Polly.Contrib organisation - you'll have full rights to this repo, to manage and deliver your awesomeness to the Polly community! - -If you already have your contribution in a github repo, we can also just move the existing repo into the Polly.Contrib org - you still retain full rights over the repo and management of the content, but the contrib gets official recognition under the Polly.Contrib banner. +policyBuilder.HedgeAsync(maxAttemptCount: 2, + hedgingDelay: TimeSpan.FromMilliseconds(100), + onHedgeAsync: context => Task.CompletedTask); +``` diff --git a/src/Polly.Contrib.RequestHedging.Specs/AsyncHedgingSpecs.cs b/src/Polly.Contrib.RequestHedging.Specs/AsyncHedgingSpecs.cs new file mode 100644 index 0000000..2876ee9 --- /dev/null +++ b/src/Polly.Contrib.RequestHedging.Specs/AsyncHedgingSpecs.cs @@ -0,0 +1,102 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Polly.Contrib.RequestHedging.Specs +{ + public class AsyncHedgingSpecs + { + [Fact] + public async Task Should_ExceptionPredicates_Invoked_But_Not_Match() + { + var hedgeCount = 0; + + await Assert.ThrowsAsync(() => Policy.Handle().HedgeAsync( + 1, TimeSpan.FromMilliseconds(100), _ => + { + Interlocked.Increment(ref hedgeCount); + + return Task.CompletedTask; + }) + .ExecuteAsync(() => throw new TimeoutException())); + + Assert.Equal(0, hedgeCount); + } + + [Fact] + public async Task Should_ExceptionPredicates_Invoked_And_Matched() + { + var hedgeCount = 0; + var invoked = false; + + await Policy.Handle().HedgeAsync( + 1, TimeSpan.FromMilliseconds(100), _ => + { + Interlocked.Increment(ref hedgeCount); + + return Task.CompletedTask; + }) + .ExecuteAsync(() => + { + if (invoked) return Task.CompletedTask; + + invoked = true; + + throw new InvalidOperationException(); + }); + + Assert.Equal(1, hedgeCount); + Assert.True(invoked); + } + + [Fact] + public Task Always_Throw_Exception_And_Match() => + Assert.ThrowsAsync(() => Policy.Handle() + .HedgeAsync(3, TimeSpan.FromMilliseconds(100)) + .ExecuteAsync(() => throw new TimeoutException())); + + [Fact] + public async Task OnHedgeAsync_Count_Should_Equal_AttemptCount() + { + const int maxAttemptCount = 10; + const int maxValue = 5; + + var hedgeCount = 0; + var invokeCount = 0; + + await Policy.Handle() + .HedgeAsync(maxAttemptCount, TimeSpan.FromMilliseconds(20), _ => + { + Interlocked.Increment(ref hedgeCount); + + return Task.CompletedTask; + }).ExecuteAsync(() => + { + if (invokeCount++ < maxValue) throw new InvalidOperationException(); + + return Task.CompletedTask; + }); + + Assert.Equal(maxValue, hedgeCount); + } + + [Fact] + public async Task OnHedgeAsync_Count_Should_Equal_MaxAttemptCount() + { + const int maxAttemptCount = 10; + + var hedgeCount = 0; + + await Assert.ThrowsAsync(() => Policy.Handle() + .HedgeAsync(maxAttemptCount, TimeSpan.FromMilliseconds(20), _ => + { + Interlocked.Increment(ref hedgeCount); + + return Task.CompletedTask; + }).ExecuteAsync(() => throw new TimeoutException())); + + Assert.Equal(maxAttemptCount, hedgeCount); + } + } +} diff --git a/src/Polly.Contrib.RequestHedging.Specs/AsyncHedgingTResultSpecs.cs b/src/Polly.Contrib.RequestHedging.Specs/AsyncHedgingTResultSpecs.cs new file mode 100644 index 0000000..b09e2cc --- /dev/null +++ b/src/Polly.Contrib.RequestHedging.Specs/AsyncHedgingTResultSpecs.cs @@ -0,0 +1,233 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Polly.Contrib.RequestHedging.Specs +{ + public class AsyncHedgingTResultSpecs + { + [Fact] + public async Task Should_ExceptionPredicates_Invoked_But_Not_Match() + { + var hedgeCount = 0; + + await Assert.ThrowsAsync(() => Policy.Handle().HedgeAsync( + 1, TimeSpan.FromMilliseconds(100), _ => + { + Interlocked.Increment(ref hedgeCount); + + return Task.CompletedTask; + }) + .ExecuteAsync(() => throw new TimeoutException())); + + Assert.Equal(0, hedgeCount); + } + + [Fact] + public async Task Should_ExceptionPredicates_Invoked_And_Matched() + { + var hedgeCount = 0; + var invoked = false; + + await Policy.Handle().HedgeAsync( + 1, TimeSpan.FromMilliseconds(100), _ => + { + Interlocked.Increment(ref hedgeCount); + + return Task.CompletedTask; + }) + .ExecuteAsync(() => + { + if (invoked) return Task.FromResult(1); + + invoked = true; + + throw new InvalidOperationException(); + }); + + Assert.Equal(1, hedgeCount); + Assert.True(invoked); + } + + [Fact] + public Task Always_Throw_Exception_And_Match() => + Assert.ThrowsAsync(() => Policy.Handle() + .HedgeAsync(3, TimeSpan.FromMilliseconds(100)) + .ExecuteAsync(() => throw new TimeoutException())); + + [Fact] + public async Task Should_ResultPredicates_Invoked() + { + var invokeCount = 0; + + await Policy.HandleResult(x => x < 1).HedgeAsync(3, TimeSpan.FromMilliseconds(100)) + .ExecuteAsync(() => Task.FromResult(invokeCount++)); + + Assert.Equal(2, invokeCount); + } + + [Fact] + public async Task OnHedgeAsync_Count_Should_Equal_AttemptCount() + { + const int maxAttemptCount = 10; + const int maxValue = 5; + + var hedgeCount = 0; + var invokeCount = 0; + + await Policy.HandleResult(x => x < maxValue) + .HedgeAsync(maxAttemptCount, TimeSpan.FromMilliseconds(20), _ => + { + Interlocked.Increment(ref hedgeCount); + + return Task.CompletedTask; + }).ExecuteAsync(() => Task.FromResult(invokeCount++)); + + Assert.Equal(maxValue, hedgeCount); + } + + [Fact] + public async Task OnHedgeAsync_Count_Should_Equal_MaxAttemptCount() + { + const int maxAttemptCount = 10; + + var hedgeCount = 0; + + await Policy.HandleResult(x => x < 10) + .HedgeAsync(maxAttemptCount, TimeSpan.FromMilliseconds(20), _ => + { + Interlocked.Increment(ref hedgeCount); + + return Task.CompletedTask; + }).ExecuteAsync(() => Task.FromResult(0)); + + Assert.Equal(maxAttemptCount, hedgeCount); + } + + [Fact] + public async Task Should_Prefer_Result_InsteadOf_Exception() + { + var invoked = false; + + Assert.False(await Policy.HandleResult(x => !x).Or() + .HedgeAsync(1, TimeSpan.FromMilliseconds(100)) + .ExecuteAsync(() => + { + if (invoked) throw new InvalidOperationException(); + + invoked = true; + + return Task.FromResult(false); + })); + } + + [Fact] + public async Task Should_Return_The_Fastest_Result() + { + var array = new[] { (500, 3), (250, 15), (200, 20) }; + var count = 0; + + Assert.Equal(15, await Policy.HandleResult(x => x < 10).HedgeAsync(array.Length - 1, TimeSpan.FromMilliseconds(100)) + .ExecuteAsync(async () => + { + var item = array[count++]; + + await Task.Delay(item.Item1); + + return item.Item2; + })); + } + + [Fact] + public async Task Should_Return_The_Fastest_Result_Slowly() + { + var array = new[] { (500, 3), (350, 15), (200, 20) }; + var count = 0; + + Assert.Equal(20, await Policy.HandleResult(x => x < 10).HedgeAsync(array.Length - 1, TimeSpan.FromMilliseconds(100)) + .ExecuteAsync(async () => + { + var item = array[count++]; + + await Task.Delay(item.Item1); + + return item.Item2; + })); + } + + [Fact] + public async Task Completed_Before_Any_Hedge_Request() + { + var invokeCount = 0; + + Assert.Equal(0, await Policy.Handle().HedgeAsync(10, TimeSpan.FromMilliseconds(1000)) + .ExecuteAsync(async () => + { + await Task.Delay(50); + + return invokeCount++; + })); + } + + [Fact] + public async Task Return_Least_Matched_Result() + { + var invokeCount = 0; + + Assert.Equal(3, await Policy.HandleResult(x => x < 5).HedgeAsync(3, TimeSpan.FromMilliseconds(100)) + .ExecuteAsync(async () => + { + await Task.Delay(50); + + return invokeCount++; + })); + } + + [Fact] + public async Task When_Cancel() + { + using (var cts = new CancellationTokenSource(500)) + { + await Assert.ThrowsAsync(() => + Policy.HandleResult(x => x < 10).HedgeAsync(10, TimeSpan.FromMilliseconds(100)) + .ExecuteAsync(async token => + { + await Task.Delay(10000, token); + + return 1; + }, cts.Token)); + } + } + + [Fact] + public async Task First_Request_Completed_Successfully_Triggers_The_Cancellation_Of_All_Others() + { + var array = new[] { (5000, 3), (3500, 15), (100, 20) }; + var count = 0; + var cancelled = 0; + + Assert.Equal(20, await Policy.HandleResult(x => x < 10).HedgeAsync(array.Length - 1, TimeSpan.FromMilliseconds(100)) + .ExecuteAsync(async token => + { + var item = array[count++]; + + try + { + await Task.Delay(item.Item1, token); + } + catch (OperationCanceledException) when (token.IsCancellationRequested) + { + Interlocked.Increment(ref cancelled); + + throw; + } + + return item.Item2; + }, CancellationToken.None)); + + Assert.Equal(2, cancelled); + + } + } +} diff --git a/src/Polly.Contrib.RequestHedging.Specs/MyContribSpecs.cs b/src/Polly.Contrib.RequestHedging.Specs/MyContribSpecs.cs deleted file mode 100644 index 82b856c..0000000 --- a/src/Polly.Contrib.RequestHedging.Specs/MyContribSpecs.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FluentAssertions; -using Xunit; - -namespace Polly.Contrib.RequestHedging.Specs // Match the namespace to the main project, then add: .Specs -{ - public class MyContribSpecs - { - [Fact] - public void ReplaceMeWithRealTests() - { - } - } -} diff --git a/src/Polly.Contrib.RequestHedging.Specs/Polly.Contrib.RequestHedging.Specs.csproj b/src/Polly.Contrib.RequestHedging.Specs/Polly.Contrib.RequestHedging.Specs.csproj index 2d22837..85f8557 100644 --- a/src/Polly.Contrib.RequestHedging.Specs/Polly.Contrib.RequestHedging.Specs.csproj +++ b/src/Polly.Contrib.RequestHedging.Specs/Polly.Contrib.RequestHedging.Specs.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + net6.0 ..\Polly.Contrib.RequestHedging.snk true @@ -11,10 +11,13 @@ - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Polly.Contrib.RequestHedging/AsyncHedgingEngine.cs b/src/Polly.Contrib.RequestHedging/AsyncHedgingEngine.cs index cdbb1ff..0401774 100644 --- a/src/Polly.Contrib.RequestHedging/AsyncHedgingEngine.cs +++ b/src/Polly.Contrib.RequestHedging/AsyncHedgingEngine.cs @@ -1,106 +1,169 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Polly.Utilities; namespace Polly.Contrib.RequestHedging { internal static class AsyncHedgingEngine { internal static async Task ImplementationAsync( - Func> action, Context context, CancellationToken cancellationToken, - Func onHedgeAsync, TimeSpan hedgingDelay, - IEnumerable>> hedgedTaskFunctions, - bool continueOnCapturedContext) + Func> action, Context context, + CancellationToken cancellationToken, ExceptionPredicates exceptionPredicates, + ResultPredicates resultPredicates, Func onHedgeAsync, TimeSpan hedgingDelay, + int maxAttemptCount, bool continueOnCapturedContext) { - IEnumerator>> hedgedTaskFunctionsEnumerator = hedgedTaskFunctions?.GetEnumerator() ?? Enumerable.Empty>>().GetEnumerator(); - - var cancellationTokenList = new List(); - var taskList = new List>(); - - try + using (var result = new HedgingResult(maxAttemptCount + 1, cancellationToken)) { - if (hedgedTaskFunctionsEnumerator.Current == null) - { - return await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); - } + var now = DateTime.Now; - taskList.Add(action(context, cancellationToken)); + result.Execute(action, context, exceptionPredicates, resultPredicates, + continueOnCapturedContext); - while (taskList.Any(x => !x.IsCompleted)) + for (var index = 0; index < maxAttemptCount; index++) { - var delayTask = SystemClock.SleepAsync(hedgingDelay, cancellationToken); - var finishedTask = await Task.WhenAny(Task.WhenAny(taskList.Where(x => !x.IsCompleted)), delayTask).ConfigureAwait(continueOnCapturedContext); + var before = DateTime.Now - now; - if (finishedTask != delayTask) + var checkTask = true; + if (hedgingDelay > before) { - // something completed before the delay, check and return the result if any - foreach (var t in taskList) - { - if (t.Status == TaskStatus.RanToCompletion) - { - return t.Result; - } - } + var delayTask = Task.Delay(hedgingDelay - before, cancellationToken); + + checkTask = await Task.WhenAny(result.Task, delayTask) + .ConfigureAwait(continueOnCapturedContext) != delayTask; } - // no result returned, so maybe there is exception - // fire off hedge request if there is any - if (hedgedTaskFunctionsEnumerator.Current != null) + // something completed before the delay, check and return the result if any + if (checkTask && result.Task.IsCompleted) { - var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - cancellationTokenList.Add(cts); - taskList.Add(hedgedTaskFunctionsEnumerator.Current(context, cts.Token)); - await onHedgeAsync(context).ConfigureAwait(false); - hedgedTaskFunctionsEnumerator.MoveNext(); + break; } + + now = DateTime.Now; + + // no result returned, so maybe there is result or exception match hedge request if there is any. + result.Execute(action, context, exceptionPredicates, resultPredicates, + continueOnCapturedContext); + + await onHedgeAsync(context).ConfigureAwait(continueOnCapturedContext); } - // all the task are completed, check if any ran to completion - foreach (var t in taskList) + return result.Task.Status == TaskStatus.RanToCompletion + ? result.Task.Result + : await result.Task.ConfigureAwait(continueOnCapturedContext); + } + } + + private class HedgingResult : IDisposable + { + private readonly TaskCompletionSource _tcs = new TaskCompletionSource(); + private readonly int _maxTasks; + private readonly CancellationTokenSource _cts; + + private Exception _ex; + private bool _hasResult; + private TResult _result; + private int _completedTasks; + private bool _disposed; + + public Task Task => _tcs.Task; + + public HedgingResult(int maxTasks, CancellationToken cancellationToken) + { + _maxTasks = maxTasks; + + _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + + _cts.Token.Register(Cancel); + } + + private void Cancel() => _tcs.TrySetCanceled(_cts.Token); + + public async void Execute(Func> action, + Context context, ExceptionPredicates exceptionPredicates, + ResultPredicates resultPredicates, bool continueOnCapturedContext) + { + try { - if (t.Status == TaskStatus.RanToCompletion) - { - return t.Result; - } - } + var result = await action(context, _cts.Token).ConfigureAwait(continueOnCapturedContext); - // still no result, throw first exception - foreach (var t in taskList) + TrySetResult(resultPredicates.AnyMatch(result), result); + } + catch (Exception ex) { - if (t.Status == TaskStatus.Faulted) - { - // this will rethrow the exception - await t.ConfigureAwait(continueOnCapturedContext); - } + TrySetException(exceptionPredicates.FirstMatchOrDefault(ex) != null, ex); } + } - cancellationToken.ThrowIfCancellationRequested(); + private void TrySetException(bool matched, Exception ex) + { + var completedTasks = Interlocked.Increment(ref _completedTasks); - // we should never reach here now - throw new InvalidOperationException(); + if (matched) + { + _ex = ex; + + TryCheckTasksHasCompletion(completedTasks); + } + else if (!_disposed && _cts.IsCancellationRequested) + { + _tcs.TrySetCanceled(_cts.Token); + } + else if (ex is OperationCanceledException oe && oe.CancellationToken.IsCancellationRequested) + { + _tcs.TrySetCanceled(oe.CancellationToken); + } + else + { + _tcs.TrySetException(ex); + } } - finally + + private void TrySetResult(bool matched, TResult result) { - // cancel all remaining tasks - foreach (var taskCts in cancellationTokenList) + var completedTasks = Interlocked.Increment(ref _completedTasks); + + if (matched) { - taskCts.Cancel(); - taskCts.Dispose(); + _hasResult = true; + _result = result; + + TryCheckTasksHasCompletion(completedTasks); } + else + { + _tcs.TrySetResult(result); + } + } - // handle any faulted tasks - foreach (var t in taskList.Where(x => x.IsFaulted)) + private void TryCheckTasksHasCompletion(int completedTasks) + { + if (completedTasks < _maxTasks) return; + + if (_hasResult) { - if (t.IsFaulted) - { - t.Exception.Handle(_ => true); - } + _tcs.TrySetResult(_result); + } + else if (!_disposed && _cts.IsCancellationRequested) + { + _tcs.TrySetCanceled(_cts.Token); + } + else if (_ex is OperationCanceledException oe && oe.CancellationToken.IsCancellationRequested) + { + _tcs.TrySetCanceled(oe.CancellationToken); + } + else + { + _tcs.TrySetException(_ex ?? new InvalidOperationException()); } + } + + public void Dispose() + { + _cts.Cancel(); + + _disposed = true; - hedgedTaskFunctionsEnumerator?.Dispose(); + _cts.Dispose(); } } } diff --git a/src/Polly.Contrib.RequestHedging/AsyncHedgingPolicy.cs b/src/Polly.Contrib.RequestHedging/AsyncHedgingPolicy.cs index 2add312..c826b1e 100644 --- a/src/Polly.Contrib.RequestHedging/AsyncHedgingPolicy.cs +++ b/src/Polly.Contrib.RequestHedging/AsyncHedgingPolicy.cs @@ -1,12 +1,56 @@ using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Polly.Contrib.RequestHedging { + /// + /// A request hedging policy that can be applied to asynchronous delegates. + /// + public class AsyncHedgingPolicy : AsyncPolicy, IHedgingPolicy + { + private readonly Func _onHedgeAsync; + + private readonly int _maxAttemptCount; + private readonly TimeSpan _hedgingDelay; + + /// + /// Initializes a new instance of the class. + /// + /// The policyBuilder. + /// The maximum number of call attempts, not contains the first call. + /// The hedgingDelay. + /// The onHedgeAsync. + internal AsyncHedgingPolicy( + PolicyBuilder policyBuilder, + int maxAttemptCount, + TimeSpan hedgingDelay, + Func onHedgeAsync) + : base(policyBuilder) + { + _maxAttemptCount = maxAttemptCount; + _hedgingDelay = hedgingDelay; + _onHedgeAsync = onHedgeAsync ?? (_ => Task.CompletedTask); + } + + /// + [DebuggerStepThrough] + protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, + bool continueOnCapturedContext) + => AsyncHedgingEngine.ImplementationAsync( + action, + context, + cancellationToken, + ExceptionPredicates, + ResultPredicates.None, + _onHedgeAsync, + _hedgingDelay, + _maxAttemptCount, + continueOnCapturedContext + ); + } + /// /// A request hedging policy that can be applied to asynchronous delegates returning a value of type . /// @@ -15,25 +59,24 @@ public class AsyncHedgingPolicy : AsyncPolicy, IHedgingPolicy< { private readonly Func _onHedgeAsync; - IEnumerable>> _hedgedTaskFunctions; - + private readonly int _maxAttemptCount; private readonly TimeSpan _hedgingDelay; /// /// Initializes a new instance of the class. /// /// The policyBuilder. - /// The hedgedTaskFunctions. + /// The maximum number of call attempts, not contains the first call. /// The hedgingDelay. /// The onHedgeAsync. internal AsyncHedgingPolicy( PolicyBuilder policyBuilder, - IEnumerable>> hedgedTaskFunctions, + int maxAttemptCount, TimeSpan hedgingDelay, Func onHedgeAsync) : base(policyBuilder) { - _hedgedTaskFunctions = hedgedTaskFunctions ?? Enumerable.Empty>>(); + _maxAttemptCount = maxAttemptCount; _hedgingDelay = hedgingDelay; _onHedgeAsync = onHedgeAsync ?? (_ => Task.CompletedTask); } @@ -46,9 +89,11 @@ protected override Task ImplementationAsync(Func - /// Fluent API for defining an . - /// - public static class AsyncHedgingTResultSyntax - { - /// - /// Builds an that will send hedge request if the action does not succeed within - /// . It will iterate through each of the - /// - /// The policy builder. - /// Hedge request function list. - /// Delay before issuing hedge request. - /// Task performed after every spawning of a hedged request. - /// The policy instance. - /// onRetry - public static AsyncHedgingPolicy HedgeAsync( - this PolicyBuilder policyBuilder, - IEnumerable>> hedgedTaskFunctions, - TimeSpan hedgingDelay, - Func onHedgeAsync = null) - { - if (hedgingDelay == default || hedgingDelay == System.Threading.Timeout.InfiniteTimeSpan) throw new ArgumentException(nameof(hedgingDelay)); - - return new AsyncHedgingPolicy( - policyBuilder, - hedgedTaskFunctions, - hedgingDelay, - onHedgeAsync); - } - - /// - /// Builds an that will send hedge request if the action does not succeed within - /// and perform via hedging. - /// - /// The policy builder. - /// Hedge request function. - /// Delay before issuing hedge request. - /// Task performed after every spawning of a hedged request - /// The policy instance. - /// onRetry - public static AsyncHedgingPolicy HedgeAsync( - this PolicyBuilder policyBuilder, - Func> hedgedTaskFunction, - TimeSpan hedgingDelay, - Func onHedgeAsync = null) - => HedgeAsync(policyBuilder, new[] { hedgedTaskFunction }, hedgingDelay, onHedgeAsync); - - /// - /// Builds an that will send hedge request if the action does not succeed within - /// and calls times. - /// - /// The policy builder. - /// Hedge request function. - /// Number of times to call the hedge function. - /// Delay before issuing hedge request. - /// Task performed after every spawning a hedged request. - /// The policy instance. - /// onRetry - public static AsyncHedgingPolicy HedgeAsync( - this PolicyBuilder policyBuilder, - Func> hedgedTaskFunction, - int hedgeCallAttempts, - TimeSpan hedgingDelay, - Func onHedgeAsync = null) - => HedgeAsync(policyBuilder, Enumerable.Repeat(hedgedTaskFunction, hedgeCallAttempts), hedgingDelay, onHedgeAsync); - } -} diff --git a/src/Polly.Contrib.RequestHedging/HedgingSyntax.cs b/src/Polly.Contrib.RequestHedging/HedgingSyntax.cs new file mode 100644 index 0000000..fad1828 --- /dev/null +++ b/src/Polly.Contrib.RequestHedging/HedgingSyntax.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading.Tasks; + +namespace Polly.Contrib.RequestHedging +{ + /// + /// Fluent API for defining an . + /// + public static class HedgingSyntax + { + /// + /// Builds an that will send hedge request if the action does not succeed within + /// and calls times. + /// + /// The policy builder. + /// The maximum number of call attempts, not contains the first call. + /// Delay before issuing hedge request. + /// Task performed after every spawning a hedged request. + /// The policy instance. + /// onRetry + public static AsyncHedgingPolicy HedgeAsync( + this PolicyBuilder policyBuilder, + int maxAttemptCount, + TimeSpan hedgingDelay, + Func onHedgeAsync = null) + => new AsyncHedgingPolicy( + policyBuilder, + maxAttemptCount, + hedgingDelay, + onHedgeAsync); + + /// + /// Builds an that will send hedge request if the action does not succeed within + /// and calls times. + /// + /// The policy builder. + /// The maximum number of call attempts, not contains the first call. + /// Delay before issuing hedge request. + /// Task performed after every spawning a hedged request. + /// The policy instance. + /// onRetry + public static AsyncHedgingPolicy HedgeAsync( + this PolicyBuilder policyBuilder, + int maxAttemptCount, + TimeSpan hedgingDelay, + Func onHedgeAsync = null) + => new AsyncHedgingPolicy( + policyBuilder, + maxAttemptCount, + hedgingDelay, + onHedgeAsync); + } +} diff --git a/src/Polly.Contrib.RequestHedging/Polly.Contrib.RequestHedging.csproj b/src/Polly.Contrib.RequestHedging/Polly.Contrib.RequestHedging.csproj index 9e1f1dc..18b93d9 100644 --- a/src/Polly.Contrib.RequestHedging/Polly.Contrib.RequestHedging.csproj +++ b/src/Polly.Contrib.RequestHedging/Polly.Contrib.RequestHedging.csproj @@ -5,10 +5,6 @@ Polly.Contrib.RequestHedging Polly.Contrib.RequestHedging 0.1.0 - 0.0.0.0 - 0.1.0.0 - 0.1.0.0 - 0.1.0 App vNext Copyright (c) 2019, App vNext and contributors Polly.Contrib.RequestHedging implements the Request Hedging policy, integrating with the Polly resilience project for .NET @@ -25,11 +21,8 @@ snupkg - + - - true -