From 49603afa734dc70da1b00ecc3060a67a55b8b536 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 14 Jun 2023 10:13:41 +0200 Subject: [PATCH 1/8] Introduce `samples` folder --- CHANGELOG.md | 7 +- Directory.Packages.props | 2 + samples/.vscode/settings.json | 3 + .../DependencyInjection.csproj | 20 +++ samples/DependencyInjection/Program.cs | 51 ++++++++ samples/Directory.Build.props | 8 ++ samples/Directory.Build.targets | 3 + samples/Extensibility/Extensibility.csproj | 18 +++ samples/Extensibility/Program.cs | 123 ++++++++++++++++++ .../GenericStrategies.csproj | 14 ++ samples/GenericStrategies/Program.cs | 74 +++++++++++ samples/Intro/Intro.csproj | 14 ++ samples/Intro/Program.cs | 74 +++++++++++ samples/README.md | 11 ++ samples/Retries/Program.cs | 113 ++++++++++++++++ samples/Retries/Retries.csproj | 14 ++ samples/Samples.sln | 68 ++++++++++ 17 files changed, 616 insertions(+), 1 deletion(-) create mode 100644 samples/.vscode/settings.json create mode 100644 samples/DependencyInjection/DependencyInjection.csproj create mode 100644 samples/DependencyInjection/Program.cs create mode 100644 samples/Directory.Build.props create mode 100644 samples/Directory.Build.targets create mode 100644 samples/Extensibility/Extensibility.csproj create mode 100644 samples/Extensibility/Program.cs create mode 100644 samples/GenericStrategies/GenericStrategies.csproj create mode 100644 samples/GenericStrategies/Program.cs create mode 100644 samples/Intro/Intro.csproj create mode 100644 samples/Intro/Program.cs create mode 100644 samples/README.md create mode 100644 samples/Retries/Program.cs create mode 100644 samples/Retries/Retries.csproj create mode 100644 samples/Samples.sln diff --git a/CHANGELOG.md b/CHANGELOG.md index ab50821910e..43983d3050f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ ## 8.0.0-alpha.1 -- The first public preview of [Polly v8](https://github.com/App-vNext/Polly/issues/1048) with our [new high-performance core API](https://github.com/App-vNext/Polly/blob/main/src/Polly.Core/README.md) and extensions - Thanks to: +- The first public preview of [Polly v8](https://github.com/App-vNext/Polly/issues/1048) with our [new high-performance core API](https://github.com/App-vNext/Polly/blob/main/src/Polly.Core/README.md) and extensions. Visit the [samples](samples/) to see Polly V8 in action. +- Releasing [`Polly.Core`](https://nuget.org/packages/Polly.Core) NuGet package. +- Releasing [`Polly.Extensions`](https://nuget.org/packages/Polly.Extensions) NuGet package. +- Releasing [`Polly.RateLimiting`](https://nuget.org/packages/Polly.RateLimiting) NuGet package. + +Thanks to: - [@adamnova](https://github.com/adamnova) - [@andrey-noskov](https://github.com/andrey-noskov) - [@joelhulen](https://github.com/joelhulen) diff --git a/Directory.Packages.props b/Directory.Packages.props index 56bc382e122..913014d59ba 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,6 +8,7 @@ + @@ -17,6 +18,7 @@ + diff --git a/samples/.vscode/settings.json b/samples/.vscode/settings.json new file mode 100644 index 00000000000..f612142721b --- /dev/null +++ b/samples/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotnet.defaultSolution": "Samples.sln" +} \ No newline at end of file diff --git a/samples/DependencyInjection/DependencyInjection.csproj b/samples/DependencyInjection/DependencyInjection.csproj new file mode 100644 index 00000000000..79128d6e49c --- /dev/null +++ b/samples/DependencyInjection/DependencyInjection.csproj @@ -0,0 +1,20 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + + + + + + + diff --git a/samples/DependencyInjection/Program.cs b/samples/DependencyInjection/Program.cs new file mode 100644 index 00000000000..5e060779e2f --- /dev/null +++ b/samples/DependencyInjection/Program.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Polly; +using Polly.Registry; +using Polly.Timeout; + +// ------------------------------------------------------------------------ +// 1. Register your resilience strategy +// ------------------------------------------------------------------------ + +var serviceProvider = new ServiceCollection() + .AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug)) + // Use "AddResilienceStrategy" extension method to configure your named strategy + .AddResilienceStrategy("my-strategy", (builder, context) => + { + // You can resolve any service from DI when building the strategy + context.ServiceProvider.GetRequiredService(); + + builder.AddTimeout(TimeSpan.FromSeconds(1)); + }) + // You can also register result-based (generic) resilience strategies + // First generic parameter is the key type, the second one is the result type + // This overload does not use the context argument (simple scenarios) + .AddResilienceStrategy("my-strategy", builder => + { + builder.AddTimeout(TimeSpan.FromSeconds(1)); + }) + .BuildServiceProvider(); + +// ------------------------------------------------------------------------ +// 2. Retrieve and use your resilience strategy +// ------------------------------------------------------------------------ + +// Resolve the resilience strategy provider for string-based keys +ResilienceStrategyProvider strategyProvider = serviceProvider.GetRequiredService>(); + +// Retrieve the strategy by name +ResilienceStrategy strategy = strategyProvider.Get("my-strategy"); + +// Retrieve the generic strategy by name +ResilienceStrategy genericStrategy = strategyProvider.Get("my-strategy"); + +try +{ + // Execute the strategy + // Notice in console output that telemetry is automatically enabled + await strategy.ExecuteAsync(async token => await Task.Delay(10000, token), CancellationToken.None); +} +catch (TimeoutRejectedException) +{ +} diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props new file mode 100644 index 00000000000..cb53b3c00ba --- /dev/null +++ b/samples/Directory.Build.props @@ -0,0 +1,8 @@ + + + true + true + true + latest + + diff --git a/samples/Directory.Build.targets b/samples/Directory.Build.targets new file mode 100644 index 00000000000..85395b0df3c --- /dev/null +++ b/samples/Directory.Build.targets @@ -0,0 +1,3 @@ + + + diff --git a/samples/Extensibility/Extensibility.csproj b/samples/Extensibility/Extensibility.csproj new file mode 100644 index 00000000000..62b027f8cc7 --- /dev/null +++ b/samples/Extensibility/Extensibility.csproj @@ -0,0 +1,18 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + + + + + diff --git a/samples/Extensibility/Program.cs b/samples/Extensibility/Program.cs new file mode 100644 index 00000000000..75bc3991558 --- /dev/null +++ b/samples/Extensibility/Program.cs @@ -0,0 +1,123 @@ +using Polly; +using Polly.Telemetry; + +// ------------------------------------------------------------------------ +// Usage of custom strategy +// ------------------------------------------------------------------------ +var strategy = new ResilienceStrategyBuilder() + // This is custom extension defined in this sample + .AddMyResilienceStrategy(new MyResilienceStrategyOptions + { + OnCustomEvent = args => + { + Console.WriteLine("OnCustomEvent"); + return default; + } + }) + .Build(); + +// Execute the strategy +strategy.Execute(() => { }); + +// ------------------------------------------------------------------------ +// SIMPLE EXTENSIBILITY MODEL (INLINE STRATEGY) +// ------------------------------------------------------------------------ + +strategy = new ResilienceStrategyBuilder() + .AddStrategy(new MySimpleStrategy()) + .Build(); + +// Execute the strategy +strategy.Execute(() => { }); + +internal class MySimpleStrategy : ResilienceStrategy +{ + protected override ValueTask> ExecuteCoreAsync(Func>> callback, ResilienceContext context, TState state) + { + Console.WriteLine("MySimpleStrategy executing!"); + + // Execute the provided callback + return callback(context, state); + } +} + +// ------------------------------------------------------------------------ +// STANDARD EXTENSIBILITY MODEL +// ------------------------------------------------------------------------ + +// ------------------------------------------------------------------------ +// 1. Create options for your custom strategy +// ------------------------------------------------------------------------ + +// 1.A Define arguments for events that your strategy uses (optional) +public readonly record struct OnCustomEventArguments(ResilienceContext Context); + +// 1.B Define the options. +public class MyResilienceStrategyOptions : ResilienceStrategyOptions +{ + public override string StrategyType => "MyCustomStrategy"; + + // Use the arguments in the delegates. + // The recommendation is to use asynchronous delegates. + public Func? OnCustomEvent { get; set; } +} + +// ------------------------------------------------------------------------ +// 2. Create a custom resilience strategy that derives from ResilienceStrategy +// ------------------------------------------------------------------------ + +// The strategy should be internal +internal class MyResilienceStrategy : ResilienceStrategy +{ + private readonly ResilienceStrategyTelemetry telemetry; + private readonly Func? onCustomEvent; + + public MyResilienceStrategy(ResilienceStrategyTelemetry telemetry, MyResilienceStrategyOptions options) + { + this.telemetry = telemetry; + this.onCustomEvent = options.OnCustomEvent; + } + + protected override async ValueTask> ExecuteCoreAsync(Func>> callback, ResilienceContext context, TState state) + { + // Here, do something before callback execution + // ... + + // Execute the provided callback + var outcome = await callback(context, state); + + // Here, do something after callback execution + // ... + + // You should report important telemetry events + telemetry.Report("MyCustomEvent", context, new OnCustomEventArguments(context)); + + // Call the delegate + onCustomEvent?.Invoke(new OnCustomEventArguments(context)); + + return outcome; + } +} + +// ------------------------------------------------------------------------ +// 3. Expose new extensions for ResilienceStrategyBuilder +// ------------------------------------------------------------------------ + +public static class MyResilienceStrategyExtensions +{ + // Add new extension that works for both "ResilienceStrategyBuilder" and "ResilienceStrategyBuilder" + public static TBuilder AddMyResilienceStrategy(this TBuilder builder, MyResilienceStrategyOptions options) + where TBuilder : ResilienceStrategyBuilderBase + { + builder.AddStrategy( + // Provide a factory that creates the strategy + context => new MyResilienceStrategy(context.Telemetry, options), + + // Pass the options, note that the instance is automatically validated by the builder + options); + + return builder; + } +} + + diff --git a/samples/GenericStrategies/GenericStrategies.csproj b/samples/GenericStrategies/GenericStrategies.csproj new file mode 100644 index 00000000000..94618308b65 --- /dev/null +++ b/samples/GenericStrategies/GenericStrategies.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/samples/GenericStrategies/Program.cs b/samples/GenericStrategies/Program.cs new file mode 100644 index 00000000000..dccc1af5cc8 --- /dev/null +++ b/samples/GenericStrategies/Program.cs @@ -0,0 +1,74 @@ +using Polly; +using Polly.Fallback; +using Polly.Retry; +using Polly.Timeout; +using System.Net; + +// ---------------------------------------------------------------------------- +// Create a generic resilience strategy using ResilienceStrategyBuilder +// ---------------------------------------------------------------------------- + +// The generic ResilienceStrategyBuilder creates a ResilienceStrategy +// that can execute synchronous and asynchronous callbacks that return T. + +ResilienceStrategy strategy = new ResilienceStrategyBuilder() + .AddFallback(new FallbackStrategyOptions + { + FallbackAction = async _ => + { + await Task.Yield(); + + // return fallback result + return new Outcome(new HttpResponseMessage(System.Net.HttpStatusCode.OK)); + }, + // You can also use switch expressions for succinct syntax + ShouldHandle = outcome => outcome switch + { + { Exception: HttpRequestException } => PredicateResult.True, + { Result: HttpResponseMessage response } when response.StatusCode == HttpStatusCode.InternalServerError => PredicateResult.True, + _ => PredicateResult.False + }, + OnFallback = _ => { Console.WriteLine("Fallback!"); return default; } + }) + .AddRetry(new RetryStrategyOptions + { + ShouldRetry = outcome => + { + // We can handle specific result + if (outcome.Result?.StatusCode == System.Net.HttpStatusCode.InternalServerError) + { + // The "PredicateResult.True" is shorthand to "new ValueTask(true)" + return PredicateResult.True; + } + + // Or exception + if ( outcome.Exception is HttpRequestException) + { + return PredicateResult.True; + } + + return PredicateResult.False; + }, + // Register user callback called whenever retry occurs + OnRetry = outcome => { Console.WriteLine($"Retrying '{outcome.Result?.StatusCode}'..."); return default; }, + BaseDelay = TimeSpan.FromMilliseconds(400), + BackoffType = RetryBackoffType.Constant, + RetryCount = 3 + }) + .AddTimeout(new TimeoutStrategyOptions + { + Timeout = TimeSpan.FromMilliseconds(500), + // Register user callback called whenever timeout occurs + OnTimeout = _ => { Console.WriteLine("Timeout occurred!"); return default; } + }) + .Build(); + +var response = await strategy.ExecuteAsync( + async token => + { + await Task.Yield(); + return new HttpResponseMessage(HttpStatusCode.InternalServerError); + }, + CancellationToken.None); + +Console.WriteLine($"Response: {response.StatusCode}"); diff --git a/samples/Intro/Intro.csproj b/samples/Intro/Intro.csproj new file mode 100644 index 00000000000..94618308b65 --- /dev/null +++ b/samples/Intro/Intro.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/samples/Intro/Program.cs b/samples/Intro/Program.cs new file mode 100644 index 00000000000..539807b340b --- /dev/null +++ b/samples/Intro/Program.cs @@ -0,0 +1,74 @@ +using Polly; +using Polly.Retry; +using Polly.Timeout; + +// ------------------------------------------------------------------------ +// 1. Create a simple resilience strategy using ResilienceStrategyBuilder +// ------------------------------------------------------------------------ + +// The ResilienceStrategyBuilder creates a ResilienceStrategy +// that can be executed synchronously or asynchronously +// and for both void and result-returning user-callbacks. +ResilienceStrategy strategy = new ResilienceStrategyBuilder() + // Use convenience extension that accepts TimeSpan + .AddTimeout(TimeSpan.FromSeconds(5)) + .Build(); + +// ------------------------------------------------------------------------ +// 2. Execute the strategy +// ------------------------------------------------------------------------ + +// synchronously +strategy.Execute(() => { }); + +// asynchronously +await strategy.ExecuteAsync(async token => { await Task.Yield(); }, CancellationToken.None); + +// synchronously with result +strategy.Execute(token => "some-result"); + +// asynchronously with result +await strategy.ExecuteAsync(async token => { await Task.Yield(); return "some-result"; }, CancellationToken.None); + +// ------------------------------------------------------------------------ +// 3. Create and execute a pipeline of strategies +// ------------------------------------------------------------------------ + +strategy = new ResilienceStrategyBuilder() + // Add retries using the options + .AddRetry(new RetryStrategyOptions + { + ShouldRetry = outcome => + { + // We want to retry on this specific exception + if (outcome.Exception is TimeoutRejectedException) + { + // The "PredicateResult.True" is shorthand to "new ValueTask(true)" + return PredicateResult.True; + } + + return PredicateResult.False; + }, + // Register user callback called whenever retry occurs + OnRetry = _ => { Console.WriteLine("Retrying..."); return default; }, + BaseDelay = TimeSpan.FromMilliseconds(400), + BackoffType = RetryBackoffType.Constant, + RetryCount = 3 + }) + // Add timeout using the options + .AddTimeout(new TimeoutStrategyOptions + { + Timeout = TimeSpan.FromMilliseconds(500), + // Register user callback called whenever timeout occurs + OnTimeout = _ => { Console.WriteLine("Timeout occurred!"); return default; } + }) + .Build(); + +try +{ + await strategy.ExecuteAsync(async token => await Task.Delay(TimeSpan.FromSeconds(2), token), CancellationToken.None); +} +catch (TimeoutRejectedException) +{ + // ok, expected +} diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 00000000000..a327353d3df --- /dev/null +++ b/samples/README.md @@ -0,0 +1,11 @@ +# Polly Samples + +This repository contains a solution with basic examples demonstrating the creation and utilization of Polly strategies. + +- [`Intro`](/Intro) - This section serves as an introduction to Polly. It demonstrates how to use `ResilienceStrategyBuilder` to create a `ResilienceStrategy`, which can be used to execute various user-provided callbacks. +- [`GenericStrategies`](/GenericStrategies) - This example showcases how to use `ResilienceStrategyBuilder` to create a generic `ResilienceStrategy`. +- [`Retries`](/Retries) - This part explains how to configure a retry resilience strategy. +- [`Extensibility`](/Extensibility) - In this part, you can learn how Polly can be extended with custom resilience strategies. +- [`DependencyInjection`](/DependencyInjection) - This section demonstrates the integration of Polly with `IServiceCollection`. + +These examples are designed as a quick-start guide to Polly. If you wish to explore more advanced scenarios and further enhance your learning, consider visiting the [Polly-Samples](https://github.com/App-vNext/Polly-Samples) repository. diff --git a/samples/Retries/Program.cs b/samples/Retries/Program.cs new file mode 100644 index 00000000000..9aa541edf90 --- /dev/null +++ b/samples/Retries/Program.cs @@ -0,0 +1,113 @@ +using Polly; +using Polly.Retry; +using System.Net; + +// ------------------------------------------------------------------------ +// 1. Create a retry strategy that only handles exceptions +// ------------------------------------------------------------------------ + +ResilienceStrategy strategy = new ResilienceStrategyBuilder() + .AddRetry(new RetryStrategyOptions + { + // Specify what exceptions should be retried + ShouldRetry = outcome => + { + if (outcome.Exception is InvalidOperationException) + { + return PredicateResult.True; + } + + return PredicateResult.False; + }, + }) + .Build(); + +// ------------------------------------------------------------------------ +// 2. Customize the retry behavior +// ------------------------------------------------------------------------ + +strategy = new ResilienceStrategyBuilder() + .AddRetry(new RetryStrategyOptions + { + // Specify what exceptions should be retried + ShouldRetry = outcome => + { + if (outcome.Exception is InvalidOperationException) + { + return PredicateResult.True; + } + + return PredicateResult.False; + }, + RetryCount = 4, + BaseDelay = TimeSpan.FromSeconds(1), + + // The recommended backoff type for HTTP scenarios + BackoffType = RetryBackoffType.ExponentialWithJitter + }) + .Build(); + +// ------------------------------------------------------------------------ +// 3. Register to callbacks +// ------------------------------------------------------------------------ + +strategy = new ResilienceStrategyBuilder() + .AddRetry(new RetryStrategyOptions + { + // Specify what exceptions should be retried + ShouldRetry = outcome => + { + if (outcome.Exception is InvalidOperationException) + { + return PredicateResult.True; + } + + return PredicateResult.False; + }, + + OnRetry = outcome => + { + Console.WriteLine($"Retrying attempt {outcome.Arguments.Attempt}..."); + return default; + } + }) + .Build(); + +// ------------------------------------------------------------------------ +// 4. Create a HTTP retry strategy that handles both exceptions and results +// ------------------------------------------------------------------------ + +ResilienceStrategy httpStrategy = new ResilienceStrategyBuilder() + .AddRetry(new RetryStrategyOptions + { + // Specify what exceptions or results should be retried + ShouldRetry = outcome => + { + // Now, also handle results + if (outcome.Result?.StatusCode == HttpStatusCode.InternalServerError) + { + return PredicateResult.True; + } + + if (outcome.Exception is InvalidOperationException) + { + return PredicateResult.True; + } + + return PredicateResult.False; + }, + + // Specify delay generator + RetryDelayGenerator = outcome => + { + if (outcome.Result is not null && outcome.Result.Headers.TryGetValues("Retry-After", out var value)) + { + // Return delay based on header + return new ValueTask(TimeSpan.FromSeconds(int.Parse(value.Single()))); + } + + // Return delay hinted by the retry strategy + return new ValueTask(outcome.Arguments.DelayHint); + } + }) + .Build(); diff --git a/samples/Retries/Retries.csproj b/samples/Retries/Retries.csproj new file mode 100644 index 00000000000..94618308b65 --- /dev/null +++ b/samples/Retries/Retries.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/samples/Samples.sln b/samples/Samples.sln new file mode 100644 index 00000000000..d821d1977a2 --- /dev/null +++ b/samples/Samples.sln @@ -0,0 +1,68 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CE7916FD-6C1A-48CE-8919-F4BAB4E3770F}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + README.md = README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extensibility", "Extensibility\Extensibility.csproj", "{1EC623B0-2B11-427B-A2B6-A22265E5C941}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Core", "..\src\Polly.Core\Polly.Core.csproj", "{E40894F4-0AE7-4247-9244-45C8C7D5B80A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Intro", "Intro\Intro.csproj", "{D23FC7B1-B549-405A-823C-CF43382C3432}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenericStrategies", "GenericStrategies\GenericStrategies.csproj", "{10175C17-01A5-4936-8966-86FB1C7891C0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Retries", "Retries\Retries.csproj", "{8A46294C-29CB-4E70-BFE0-5DE386437C50}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjection", "DependencyInjection\DependencyInjection.csproj", "{9B8BFE03-4457-4C55-91AD-4096DDE622C3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Extensions", "..\src\Polly.Extensions\Polly.Extensions.csproj", "{AA72D32A-4A3A-4C49-BA64-BC4B8AF49B87}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1EC623B0-2B11-427B-A2B6-A22265E5C941}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EC623B0-2B11-427B-A2B6-A22265E5C941}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EC623B0-2B11-427B-A2B6-A22265E5C941}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EC623B0-2B11-427B-A2B6-A22265E5C941}.Release|Any CPU.Build.0 = Release|Any CPU + {E40894F4-0AE7-4247-9244-45C8C7D5B80A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E40894F4-0AE7-4247-9244-45C8C7D5B80A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E40894F4-0AE7-4247-9244-45C8C7D5B80A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E40894F4-0AE7-4247-9244-45C8C7D5B80A}.Release|Any CPU.Build.0 = Release|Any CPU + {D23FC7B1-B549-405A-823C-CF43382C3432}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D23FC7B1-B549-405A-823C-CF43382C3432}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D23FC7B1-B549-405A-823C-CF43382C3432}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D23FC7B1-B549-405A-823C-CF43382C3432}.Release|Any CPU.Build.0 = Release|Any CPU + {10175C17-01A5-4936-8966-86FB1C7891C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10175C17-01A5-4936-8966-86FB1C7891C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10175C17-01A5-4936-8966-86FB1C7891C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10175C17-01A5-4936-8966-86FB1C7891C0}.Release|Any CPU.Build.0 = Release|Any CPU + {8A46294C-29CB-4E70-BFE0-5DE386437C50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A46294C-29CB-4E70-BFE0-5DE386437C50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A46294C-29CB-4E70-BFE0-5DE386437C50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A46294C-29CB-4E70-BFE0-5DE386437C50}.Release|Any CPU.Build.0 = Release|Any CPU + {9B8BFE03-4457-4C55-91AD-4096DDE622C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B8BFE03-4457-4C55-91AD-4096DDE622C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B8BFE03-4457-4C55-91AD-4096DDE622C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B8BFE03-4457-4C55-91AD-4096DDE622C3}.Release|Any CPU.Build.0 = Release|Any CPU + {AA72D32A-4A3A-4C49-BA64-BC4B8AF49B87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA72D32A-4A3A-4C49-BA64-BC4B8AF49B87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA72D32A-4A3A-4C49-BA64-BC4B8AF49B87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA72D32A-4A3A-4C49-BA64-BC4B8AF49B87}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {67CA3053-C929-4CAE-99A3-40CF1894FE2B} + EndGlobalSection +EndGlobal From 6f0e641bf6641f5827e8e76f7fabd8f37b6b2584 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 15 Jun 2023 10:05:49 +0200 Subject: [PATCH 2/8] Polish release notes. --- CHANGELOG.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43983d3050f..96d6f26c032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ ## 8.0.0-alpha.1 -- The first public preview of [Polly v8](https://github.com/App-vNext/Polly/issues/1048) with our [new high-performance core API](https://github.com/App-vNext/Polly/blob/main/src/Polly.Core/README.md) and extensions. Visit the [samples](samples/) to see Polly V8 in action. -- Releasing [`Polly.Core`](https://nuget.org/packages/Polly.Core) NuGet package. -- Releasing [`Polly.Extensions`](https://nuget.org/packages/Polly.Extensions) NuGet package. -- Releasing [`Polly.RateLimiting`](https://nuget.org/packages/Polly.RateLimiting) NuGet package. +- The first public preview of [Polly v8](https://github.com/App-vNext/Polly/issues/1048) with our [new high-performance core API](https://github.com/App-vNext/Polly/blob/main/src/Polly.Core/README.md) and extensions. Feel free to check out the [samples](samples/) to see the new and improved Polly V8 in action. +- The first release of the new NuGet packages: + - [`Polly.Core`](https://nuget.org/packages/Polly.Core) - This package contains the new Polly V8 API. + - [`Polly.Extensions`](https://nuget.org/packages/Polly.Extensions) - This package is designed to integrate Polly with dependency injection and enable telemetry. + - [`Polly.RateLimiting`](https://nuget.org/packages/Polly.RateLimiting) - This package provides an integration between Polly and [`System.Threading.RateLimiting`](https://www.nuget.org/packages/System.Threading.RateLimiting/). Thanks to: - [@adamnova](https://github.com/adamnova) From cddd37d57a691bbd9a25d898a7832555e8a710db Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 15 Jun 2023 17:19:54 +0200 Subject: [PATCH 3/8] Use the Polly package --- Directory.Packages.props | 5 ++++- .../DependencyInjection/DependencyInjection.csproj | 6 +----- samples/Extensibility/Extensibility.csproj | 8 ++------ samples/GenericStrategies/GenericStrategies.csproj | 4 ++-- samples/Intro/Intro.csproj | 4 ++-- samples/Retries/Retries.csproj | 4 ++-- samples/Samples.sln | 12 ------------ 7 files changed, 13 insertions(+), 30 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 913014d59ba..ee7e1d5f9ed 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,6 +1,7 @@ true + 8.0.0-alpha.1 @@ -18,7 +19,9 @@ - + + + diff --git a/samples/DependencyInjection/DependencyInjection.csproj b/samples/DependencyInjection/DependencyInjection.csproj index 79128d6e49c..f4628e7cdee 100644 --- a/samples/DependencyInjection/DependencyInjection.csproj +++ b/samples/DependencyInjection/DependencyInjection.csproj @@ -10,11 +10,7 @@ - - - - - + diff --git a/samples/Extensibility/Extensibility.csproj b/samples/Extensibility/Extensibility.csproj index 62b027f8cc7..201d8ee38e0 100644 --- a/samples/Extensibility/Extensibility.csproj +++ b/samples/Extensibility/Extensibility.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,11 +8,7 @@ - - - - - + diff --git a/samples/GenericStrategies/GenericStrategies.csproj b/samples/GenericStrategies/GenericStrategies.csproj index 94618308b65..ee418caeadd 100644 --- a/samples/GenericStrategies/GenericStrategies.csproj +++ b/samples/GenericStrategies/GenericStrategies.csproj @@ -8,7 +8,7 @@ - + - + diff --git a/samples/Intro/Intro.csproj b/samples/Intro/Intro.csproj index 94618308b65..8321197231a 100644 --- a/samples/Intro/Intro.csproj +++ b/samples/Intro/Intro.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,7 +8,7 @@ - + diff --git a/samples/Retries/Retries.csproj b/samples/Retries/Retries.csproj index 94618308b65..201d8ee38e0 100644 --- a/samples/Retries/Retries.csproj +++ b/samples/Retries/Retries.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,7 +8,7 @@ - + diff --git a/samples/Samples.sln b/samples/Samples.sln index d821d1977a2..056777613b5 100644 --- a/samples/Samples.sln +++ b/samples/Samples.sln @@ -12,8 +12,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extensibility", "Extensibility\Extensibility.csproj", "{1EC623B0-2B11-427B-A2B6-A22265E5C941}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Core", "..\src\Polly.Core\Polly.Core.csproj", "{E40894F4-0AE7-4247-9244-45C8C7D5B80A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Intro", "Intro\Intro.csproj", "{D23FC7B1-B549-405A-823C-CF43382C3432}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenericStrategies", "GenericStrategies\GenericStrategies.csproj", "{10175C17-01A5-4936-8966-86FB1C7891C0}" @@ -22,8 +20,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Retries", "Retries\Retries. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjection", "DependencyInjection\DependencyInjection.csproj", "{9B8BFE03-4457-4C55-91AD-4096DDE622C3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Extensions", "..\src\Polly.Extensions\Polly.Extensions.csproj", "{AA72D32A-4A3A-4C49-BA64-BC4B8AF49B87}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,10 +30,6 @@ Global {1EC623B0-2B11-427B-A2B6-A22265E5C941}.Debug|Any CPU.Build.0 = Debug|Any CPU {1EC623B0-2B11-427B-A2B6-A22265E5C941}.Release|Any CPU.ActiveCfg = Release|Any CPU {1EC623B0-2B11-427B-A2B6-A22265E5C941}.Release|Any CPU.Build.0 = Release|Any CPU - {E40894F4-0AE7-4247-9244-45C8C7D5B80A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E40894F4-0AE7-4247-9244-45C8C7D5B80A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E40894F4-0AE7-4247-9244-45C8C7D5B80A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E40894F4-0AE7-4247-9244-45C8C7D5B80A}.Release|Any CPU.Build.0 = Release|Any CPU {D23FC7B1-B549-405A-823C-CF43382C3432}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D23FC7B1-B549-405A-823C-CF43382C3432}.Debug|Any CPU.Build.0 = Debug|Any CPU {D23FC7B1-B549-405A-823C-CF43382C3432}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -54,10 +46,6 @@ Global {9B8BFE03-4457-4C55-91AD-4096DDE622C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B8BFE03-4457-4C55-91AD-4096DDE622C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {9B8BFE03-4457-4C55-91AD-4096DDE622C3}.Release|Any CPU.Build.0 = Release|Any CPU - {AA72D32A-4A3A-4C49-BA64-BC4B8AF49B87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AA72D32A-4A3A-4C49-BA64-BC4B8AF49B87}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA72D32A-4A3A-4C49-BA64-BC4B8AF49B87}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AA72D32A-4A3A-4C49-BA64-BC4B8AF49B87}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 5128d73e3cce9727c9ed8096832d4877abe53a33 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Fri, 16 Jun 2023 07:26:47 +0200 Subject: [PATCH 4/8] clenaup --- samples/Extensibility/Program.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/samples/Extensibility/Program.cs b/samples/Extensibility/Program.cs index 75bc3991558..b8ecbb5d52f 100644 --- a/samples/Extensibility/Program.cs +++ b/samples/Extensibility/Program.cs @@ -24,6 +24,7 @@ // ------------------------------------------------------------------------ strategy = new ResilienceStrategyBuilder() + // Just add the strategy instance directly .AddStrategy(new MySimpleStrategy()) .Build(); @@ -92,8 +93,11 @@ protected override async ValueTask> ExecuteCoreAsync(this TBuilder builder, MyResilienceStrategyOptions options) where TBuilder : ResilienceStrategyBuilderBase { + // Validate arguments + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(options); + builder.AddStrategy( // Provide a factory that creates the strategy context => new MyResilienceStrategy(context.Telemetry, options), - // Pass the options, note that the instance is automatically validated by the builder + // Pass the options, note that the options instance is automatically validated by the builder options); return builder; From 4b011253a9b4bdc526149f5c00f18731da4c9bf9 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Fri, 16 Jun 2023 09:37:43 +0200 Subject: [PATCH 5/8] PR comments --- samples/DependencyInjection/Program.cs | 1 + samples/Extensibility/Program.cs | 16 ++++++++++------ samples/Intro/Program.cs | 18 +++++++++++++----- samples/Retries/Program.cs | 7 ++++--- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/samples/DependencyInjection/Program.cs b/samples/DependencyInjection/Program.cs index 5e060779e2f..48bc399b4b3 100644 --- a/samples/DependencyInjection/Program.cs +++ b/samples/DependencyInjection/Program.cs @@ -48,4 +48,5 @@ } catch (TimeoutRejectedException) { + Console.WriteLine("Timeout!"); } diff --git a/samples/Extensibility/Program.cs b/samples/Extensibility/Program.cs index b8ecbb5d52f..72c88b9b4cd 100644 --- a/samples/Extensibility/Program.cs +++ b/samples/Extensibility/Program.cs @@ -37,6 +37,14 @@ protected override ValueTask> ExecuteCoreAsync { Console.WriteLine("MySimpleStrategy executing!"); + // The "context" holds information about execution mode + Console.WriteLine("context.IsSynchronous: {0}", context.IsSynchronous); + Console.WriteLine("context.ResultType: {0}", context.ResultType); + Console.WriteLine("context.IsVoid: {0}", context.IsVoid); + + // The "state" is ambient value passed by the caller that holds his state. + // Here, we do not do anything with it, just pass it to the callback. + // Execute the provided callback return callback(context, state); } @@ -67,7 +75,7 @@ public class MyResilienceStrategyOptions : ResilienceStrategyOptions // 2. Create a custom resilience strategy that derives from ResilienceStrategy // ------------------------------------------------------------------------ -// The strategy should be internal +// The strategy should be internal and not exposed as part of public API. Instead, expose options and extensions for resilience strategy builder. internal class MyResilienceStrategy : ResilienceStrategy { private readonly ResilienceStrategyTelemetry telemetry; @@ -90,7 +98,7 @@ protected override async ValueTask> ExecuteCoreAsync(this TBuilder builder, MyResilienceStrategyOptions options) where TBuilder : ResilienceStrategyBuilderBase { - // Validate arguments - ArgumentNullException.ThrowIfNull(builder); - ArgumentNullException.ThrowIfNull(options); - builder.AddStrategy( // Provide a factory that creates the strategy context => new MyResilienceStrategy(context.Telemetry, options), diff --git a/samples/Intro/Program.cs b/samples/Intro/Program.cs index 539807b340b..5e19566c71e 100644 --- a/samples/Intro/Program.cs +++ b/samples/Intro/Program.cs @@ -18,18 +18,21 @@ // 2. Execute the strategy // ------------------------------------------------------------------------ -// synchronously +// Synchronously strategy.Execute(() => { }); -// asynchronously +// Asynchronously await strategy.ExecuteAsync(async token => { await Task.Yield(); }, CancellationToken.None); -// synchronously with result +// Synchronously with result strategy.Execute(token => "some-result"); -// asynchronously with result +// Asynchronously with result await strategy.ExecuteAsync(async token => { await Task.Yield(); return "some-result"; }, CancellationToken.None); +// Use state to avoid lambda allocation +strategy.Execute(static state => state, "my-state"); + // ------------------------------------------------------------------------ // 3. Create and execute a pipeline of strategies // ------------------------------------------------------------------------ @@ -60,7 +63,12 @@ { Timeout = TimeSpan.FromMilliseconds(500), // Register user callback called whenever timeout occurs - OnTimeout = _ => { Console.WriteLine("Timeout occurred!"); return default; } + OnTimeout = args => + { + + Console.WriteLine($"Timeout occurred after {args.Timeout}!"); + return default; + } }) .Build(); diff --git a/samples/Retries/Program.cs b/samples/Retries/Program.cs index 9aa541edf90..405ed29fe14 100644 --- a/samples/Retries/Program.cs +++ b/samples/Retries/Program.cs @@ -3,7 +3,7 @@ using System.Net; // ------------------------------------------------------------------------ -// 1. Create a retry strategy that only handles exceptions +// 1. Create a retry strategy that only handles invalid operation exceptions // ------------------------------------------------------------------------ ResilienceStrategy strategy = new ResilienceStrategyBuilder() @@ -43,12 +43,13 @@ BaseDelay = TimeSpan.FromSeconds(1), // The recommended backoff type for HTTP scenarios + // https://www.baeldung.com/resilience4j-backoff-jitter BackoffType = RetryBackoffType.ExponentialWithJitter }) .Build(); // ------------------------------------------------------------------------ -// 3. Register to callbacks +// 3. Register the callbacks // ------------------------------------------------------------------------ strategy = new ResilienceStrategyBuilder() @@ -74,7 +75,7 @@ .Build(); // ------------------------------------------------------------------------ -// 4. Create a HTTP retry strategy that handles both exceptions and results +// 4. Create an HTTP retry strategy that handles both exceptions and results // ------------------------------------------------------------------------ ResilienceStrategy httpStrategy = new ResilienceStrategyBuilder() From ef71c624cb6c384633bb51d021942d2c42d1e6a2 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Fri, 16 Jun 2023 09:40:27 +0200 Subject: [PATCH 6/8] Improvements --- samples/DependencyInjection/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/DependencyInjection/Program.cs b/samples/DependencyInjection/Program.cs index 48bc399b4b3..c75982ddf15 100644 --- a/samples/DependencyInjection/Program.cs +++ b/samples/DependencyInjection/Program.cs @@ -48,5 +48,6 @@ } catch (TimeoutRejectedException) { + // The timeout strategy cancels the user callback and throws this exception Console.WriteLine("Timeout!"); } From 2f7fe4a6ec729bcbd1faea2aaed1b5bc41fee72d Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Fri, 16 Jun 2023 10:03:57 +0200 Subject: [PATCH 7/8] PR comments --- samples/Extensibility/Program.cs | 4 ++-- samples/Retries/Program.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/Extensibility/Program.cs b/samples/Extensibility/Program.cs index 72c88b9b4cd..e59e9f006ed 100644 --- a/samples/Extensibility/Program.cs +++ b/samples/Extensibility/Program.cs @@ -42,7 +42,7 @@ protected override ValueTask> ExecuteCoreAsync Console.WriteLine("context.ResultType: {0}", context.ResultType); Console.WriteLine("context.IsVoid: {0}", context.IsVoid); - // The "state" is ambient value passed by the caller that holds his state. + // The "state" is an ambient value passed by the caller that holds the state. // Here, we do not do anything with it, just pass it to the callback. // Execute the provided callback @@ -75,7 +75,7 @@ public class MyResilienceStrategyOptions : ResilienceStrategyOptions // 2. Create a custom resilience strategy that derives from ResilienceStrategy // ------------------------------------------------------------------------ -// The strategy should be internal and not exposed as part of public API. Instead, expose options and extensions for resilience strategy builder. +// The strategy should be internal and not exposed as part of any public API. Instead, expose options and extensions for resilience strategy builder. internal class MyResilienceStrategy : ResilienceStrategy { private readonly ResilienceStrategyTelemetry telemetry; diff --git a/samples/Retries/Program.cs b/samples/Retries/Program.cs index 405ed29fe14..c567461ae5e 100644 --- a/samples/Retries/Program.cs +++ b/samples/Retries/Program.cs @@ -43,7 +43,7 @@ BaseDelay = TimeSpan.FromSeconds(1), // The recommended backoff type for HTTP scenarios - // https://www.baeldung.com/resilience4j-backoff-jitter + // See here for more information: https://github.com/App-vNext/Polly/wiki/Retry-with-jitter#more-complex-jitter BackoffType = RetryBackoffType.ExponentialWithJitter }) .Build(); From 58b662a26dcb60392ed18937a5a1c42d4b31951a Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Fri, 16 Jun 2023 10:59:48 +0200 Subject: [PATCH 8/8] PR comments --- samples/GenericStrategies/Program.cs | 11 ++++++----- samples/Intro/Program.cs | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/samples/GenericStrategies/Program.cs b/samples/GenericStrategies/Program.cs index dccc1af5cc8..7aa5bb22319 100644 --- a/samples/GenericStrategies/Program.cs +++ b/samples/GenericStrategies/Program.cs @@ -16,14 +16,15 @@ { FallbackAction = async _ => { - await Task.Yield(); + await Task.Delay(10); - // return fallback result + // Return fallback result return new Outcome(new HttpResponseMessage(System.Net.HttpStatusCode.OK)); }, // You can also use switch expressions for succinct syntax ShouldHandle = outcome => outcome switch { + // The "PredicateResult.True" is shorthand to "new ValueTask(true)" { Exception: HttpRequestException } => PredicateResult.True, { Result: HttpResponseMessage response } when response.StatusCode == HttpStatusCode.InternalServerError => PredicateResult.True, _ => PredicateResult.False @@ -35,9 +36,8 @@ ShouldRetry = outcome => { // We can handle specific result - if (outcome.Result?.StatusCode == System.Net.HttpStatusCode.InternalServerError) + if (outcome.Result?.StatusCode == HttpStatusCode.InternalServerError) { - // The "PredicateResult.True" is shorthand to "new ValueTask(true)" return PredicateResult.True; } @@ -66,7 +66,8 @@ var response = await strategy.ExecuteAsync( async token => { - await Task.Yield(); + await Task.Delay(10); + // This causes the action fail, thus using the fallback strategy above return new HttpResponseMessage(HttpStatusCode.InternalServerError); }, CancellationToken.None); diff --git a/samples/Intro/Program.cs b/samples/Intro/Program.cs index 5e19566c71e..91fe0100080 100644 --- a/samples/Intro/Program.cs +++ b/samples/Intro/Program.cs @@ -22,13 +22,13 @@ strategy.Execute(() => { }); // Asynchronously -await strategy.ExecuteAsync(async token => { await Task.Yield(); }, CancellationToken.None); +await strategy.ExecuteAsync(async token => { await Task.Delay(10); }, CancellationToken.None); // Synchronously with result strategy.Execute(token => "some-result"); // Asynchronously with result -await strategy.ExecuteAsync(async token => { await Task.Yield(); return "some-result"; }, CancellationToken.None); +await strategy.ExecuteAsync(async token => { await Task.Delay(10); return "some-result"; }, CancellationToken.None); // Use state to avoid lambda allocation strategy.Execute(static state => state, "my-state"); @@ -53,7 +53,7 @@ return PredicateResult.False; }, // Register user callback called whenever retry occurs - OnRetry = _ => { Console.WriteLine("Retrying..."); return default; }, + OnRetry = args => { Console.WriteLine($"Retrying...{args.Arguments.Attempt} attempt"); return default; }, BaseDelay = TimeSpan.FromMilliseconds(400), BackoffType = RetryBackoffType.Constant, RetryCount = 3