From 9a1cfb75f36dd552b3d713cd1a69ffad741d9305 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 7 Sep 2023 10:23:21 +0200 Subject: [PATCH 1/3] [Docs] Dependency injection --- docs/dependency-injection.md | 225 ++++++++++++++++++++++- docs/strategies/hedging.md | 10 +- src/Polly.Extensions/README.md | 47 +---- src/Snippets/Docs/DependencyInjection.cs | 193 +++++++++++++++++++ src/Snippets/Docs/Telemetry.cs | 22 ++- src/Snippets/Extensions/Snippets.cs | 69 ------- 6 files changed, 446 insertions(+), 120 deletions(-) create mode 100644 src/Snippets/Docs/DependencyInjection.cs delete mode 100644 src/Snippets/Extensions/Snippets.cs diff --git a/docs/dependency-injection.md b/docs/dependency-injection.md index 829ed5afae2..e68e5bef052 100644 --- a/docs/dependency-injection.md +++ b/docs/dependency-injection.md @@ -1,3 +1,224 @@ -# Dependency Injection +# Dependency injection -🚧 This documentation is being written as part of the Polly v8 release. +Starting with version 8, Polly provides features that make the integration of Polly with the standard [`IServiceCollection`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection) Dependency Injection (DI) container more streamlined. This is a thin layer atop the [resilience pipeline registry](resilience-pipeline-registry.md) which manages resilience pipelines. + +## Usage + +To use the DI functionality, add the `Polly.Extensions` package to your project: + +```sh +dotnet add package Polly.Extensions +``` + +Afterwards, you can use the `AddResiliencePipeline(...)` extension method to set up your pipeline: + + +```cs +var services = new ServiceCollection(); + +// Define a resilience pipeline +services.AddResiliencePipeline("my-key", builder => +{ + // Add strategies to your pipeline here, timeout for example + builder.AddTimeout(TimeSpan.FromSeconds(10)); +}); + +// You can also access IServiceProvider by using the alternate overload +services.AddResiliencePipeline("my-key", (builder, context) => +{ + // Resolve any service from DI + var loggerFactory = context.ServiceProvider.GetRequiredService(); + + // Add strategies to your pipeline here + builder.AddTimeout(TimeSpan.FromSeconds(10)); +}); + +// Resolve the resilience pipeline +ServiceProvider serviceProvider = services.BuildServiceProvider(); +ResiliencePipelineProvider pipelineProvider = serviceProvider.GetRequiredService>(); +ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-key"); + +// Use it +await pipeline.ExecuteAsync(async cancellation => await Task.Delay(100, cancellation)); +``` + + +The `AddResiliencePipeline` extension method also registers the following services into the DI: + +- `ResiliencePipelineRegistry`: Allows adding and retrieving resilience pipelines. +- `ResiliencePipelineProvider`: Allows retrieving resilience pipelines. +- `IOptions>`: Options for `ResiliencePipelineRegistry`. + +> [!NOTE] The generic `string`` is inferred since the pipeline was defined using the "my-key" value. + +If you only need the registry without defining a pipeline, use the `AddResiliencePipelineRegistry(...)` method. + +### Generic resilience pipelines + +You can also define generic resilience pipelines (`ResiliencePipeline`), as demonstrated below: + + +```cs +var services = new ServiceCollection(); + +// Define a generic resilience pipeline +// First parameter is the type of key, second one is the type of the results the generic pipeline works with +services.AddResiliencePipeline("my-pipeline", builder => +{ + builder.AddRetry(new() + { + MaxRetryAttempts = 2, + ShouldHandle = new PredicateBuilder() + .Handle() + .Handle() + .HandleResult(response => response.StatusCode == System.Net.HttpStatusCode.InternalServerError) + }) + .AddTimeout(TimeSpan.FromSeconds(2)); +}); + +// Resolve the resilience pipeline +ServiceProvider serviceProvider = services.BuildServiceProvider(); +ResiliencePipelineProvider pipelineProvider = serviceProvider.GetRequiredService>(); +ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-key"); + +// Use it +await pipeline.ExecuteAsync( + async cancellation => await client.GetAsync(endpoint, cancellation), + cancellationToken); +``` + + +## Dynamic reloads + +The dynamic reloads is a feature of pipeline registry that is also surfaced when using the `AddResiliencePipeline(...)` extension method. Use an overload that provides access to `AddResiliencePipelineContext`: + + +```cs +services + .Configure("my-retry-options", configurationSection) // Configure the options + .AddResiliencePipeline("my-pipeline", (builder, context) => + { + // Enable the reloads whenever the named options change + context.EnableReloads("my-retry-options"); + + // Utility method to retrieve the named options + var retryOptions = context.GetOptions("my-retry-options"); + + // Add retries using the resolved options + builder.AddRetry(retryOptions); + }); +``` + + +- `EnableReloads` activates the dynamic reloading of `my-pipeline`. +- `RetryStrategyOptions` are fetched using `context.GetOptions(...)` utility method. +- A retry strategy is added. + +During a reload: + +- The callback re-executes. +- The previous pipeline is discarded. + +If an error occurs during reloading, the old pipeline remains, and dynamic reloading stops. + +## Resource disposal + +Like dynamic reloads, the pipeline registry's resource disposal feature lets you register callbacks. These callbacks run when the pipeline is discarded, reloaded, or the registry is disposed at application shutdown. + +See the example below: + + +```cs +services.AddResiliencePipeline("my-pipeline", (builder, context) => +{ + // Create disposable resource + var limiter = new ConcurrencyLimiter(new ConcurrencyLimiterOptions { PermitLimit = 100, QueueLimit = 100 }); + + // Use it + builder.AddRateLimiter(limiter); + + // Dispose the resource created in the callback when the pipeline is discarded + context.OnPipelineDisposed(() => limiter.Dispose()); +}); +``` + + +This feature ensures that resources are properly disposed when a pipeline reloads, discarding the old version. + +## Complex pipeline keys + +The `AddResiliencePipeline(...)` method supports complex pipeline keys. This capability allows you to define the structure of your pipeline and dynamically resolve and cache multiple instances of the pipeline with different keys. + +Start by defining your complex key: + + +```cs +public record struct MyPipelineKey(string PipelineName, string InstanceName) +{ +} +``` + + +Next, register your pipeline: + + +```cs +services.AddResiliencePipeline(new MyPipelineKey("my-pipeline", string.Empty), builder => +{ + // Circuit breaker is a stateful strategy. To isolate the builder across different pipelines, + // we must use multiple instances. + builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions()); +}); +``` + + +The "my-pipeline" is now registered. Note that the `InstanceName` is an empty string. While we're registering the builder action for a specific pipeline, the `InstanceName` parameter isn't used during the pipeline's registration. Some further modifications are required for this to function. + +Introduce the `PipelineNameComparer`: + + +```cs +public sealed class PipelineNameComparer : IEqualityComparer +{ + public bool Equals(MyPipelineKey x, MyPipelineKey y) => x.PipelineName == y.PipelineName; + + public int GetHashCode(MyPipelineKey obj) => (obj.PipelineName, obj.InstanceName).GetHashCode(); +} +``` + + +Then, configure the registry behavior: + + +```cs +services + .AddResiliencePipelineRegistry(options => + { + options.BuilderComparer = new PipelineNameComparer(); + + options.InstanceNameFormatter = key => key.InstanceName; + + options.BuilderNameFormatter = key => key.PipelineName; + }); +``` + + +Let's summarize our actions: + +- We assigned the `PipelineNameComparer` instance to the `BuilderComparer` property. This action changes the default registry behavior, ensuring that only the `PipelineName` is used to find the associated builder. +- We used the `InstanceNameFormatter` delegate to represent the `MyPipelineKey` as an instance name for telemetry purposes, keeping the instance name as it is. +- Likewise, the `BuilderNameFormatter` delegate represents the `MyPipelineKey` as a builder name in telemetry. + +Finally, utilize the `ResiliencePipelineProvider` to dynamically create and cache multiple instances of the same pipeline: + + +```cs +ResiliencePipelineProvider pipelineProvider = serviceProvider.GetRequiredService>(); + +// The registry dynamically creates and caches instance-A using the associated builder action +ResiliencePipeline instanceA = pipelineProvider.GetPipeline(new MyPipelineKey("my-pipeline", "instance-A")); + +// The registry creates and caches instance-B +ResiliencePipeline instanceB = pipelineProvider.GetPipeline(new MyPipelineKey("my-pipeline", "instance-B")); +``` + diff --git a/docs/strategies/hedging.md b/docs/strategies/hedging.md index 1ab4e40593e..e91eccf747a 100644 --- a/docs/strategies/hedging.md +++ b/docs/strategies/hedging.md @@ -68,7 +68,7 @@ new ResiliencePipelineBuilder() | `DelayGenerator` | `null` | Used for generating custom delays for hedging. If `null` then `Delay` is used. | | `OnHedging` | `null` | Event that is raised when a hedging is performed. | -You can use the following special values for `Delay` or in `HedgingDelayGenerator`: +You can use the following special values for `Delay` or in `DelayGenerator`: - `0 seconds` - the hedging strategy immediately creates a total of `MaxHedgedAttempts` and completes when the fastest acceptable result is available. - `-1 millisecond` - this value indicates that the strategy does not create a new hedged task before the previous one completes. This enables scenarios where having multiple concurrent hedged tasks can cause side effects. @@ -109,10 +109,10 @@ The hedging strategy operates in parallel mode when the `Delay` property is set ### Dynamic mode -In dynamic mode, you have the flexibility to control how the hedging strategy behaves during each execution. This control is achieved through the `HedgingDelayGenerator` property. +In dynamic mode, you have the flexibility to control how the hedging strategy behaves during each execution. This control is achieved through the `DelayGenerator` property. > [!NOTE] -> The `Delay` property is disregarded when `HedgingDelayGenerator` is set. +> The `Delay` property is disregarded when `DelayGenerator` is set. Example scenario: @@ -166,12 +166,12 @@ new ResiliencePipelineBuilder() // Here, we can access the original callback and return it or return a completely new action var callback = args.Callback; - // A delegate that returns a ValueTask> is required. + // A function that returns a ValueTask> is required. return async () => { try { - // A dedicated ActionContext is provided for each hedged action. + // A dedicated ActionContext is provided for each hedged action // It comes with a separate CancellationToken created specifically for this hedged attempt, // which can be cancelled later if needed. // diff --git a/src/Polly.Extensions/README.md b/src/Polly.Extensions/README.md index 9994d4ca2f5..19499c8af96 100644 --- a/src/Polly.Extensions/README.md +++ b/src/Polly.Extensions/README.md @@ -1,45 +1,6 @@ -# Polly.Extensions Overview +# Polly.extensions overview -`Polly.Extensions` provides a set of features that streamline the integration of Polly with the standard `IServiceCollection` Dependency Injection (DI) container. It further enhances telemetry by exposing a `ConfigureTelemetry` extension method that enables [logging](https://learn.microsoft.com/dotnet/core/extensions/logging?tabs=command-line) and [metering](https://learn.microsoft.com/dotnet/core/diagnostics/metrics) for all strategies created via DI extension points. +This project provides the following features: -Below is an example illustrating the usage of `AddResiliencePipeline` extension method: - - -```cs -var services = new ServiceCollection(); - -// Define a resilience pipeline -services.AddResiliencePipeline( - "my-key", - builder => builder.AddTimeout(TimeSpan.FromSeconds(10))); - -// Define a resilience pipeline with custom options -services - .Configure(options => options.Timeout = TimeSpan.FromSeconds(10)) - .AddResiliencePipeline( - "my-timeout", - (builder, context) => - { - var myOptions = context.GetOptions(); - - builder.AddTimeout(myOptions.Timeout); - }); - -// Resolve the resilience pipeline -var serviceProvider = services.BuildServiceProvider(); -var pipelineProvider = serviceProvider.GetRequiredService>(); -var pipeline = pipelineProvider.GetPipeline("my-key"); - -// Use it -await pipeline.ExecuteAsync(async cancellation => await Task.Delay(100, cancellation)); -``` - - -> [!NOTE] -> Telemetry is enabled by default when utilizing the `AddResiliencePipeline(...)` extension method. - -## Telemetry Features - -This project implements the `TelemetryListener` and uses it to bridge the Polly-native events into logs and metrics. - -Explore [telemetry documentation](../../docs/telemetry.md) for more details. +- Incorporates [dependency injection](../../docs/dependency-injection.md) support and integrates with `IServiceCollection`. +- Offers [telemetry](../../docs/telemetry.md) support. This is achieved by implementing the `TelemetryListener` and utilizing it to translate the native Polly events into logs and metrics. diff --git a/src/Snippets/Docs/DependencyInjection.cs b/src/Snippets/Docs/DependencyInjection.cs new file mode 100644 index 00000000000..eed00eb3223 --- /dev/null +++ b/src/Snippets/Docs/DependencyInjection.cs @@ -0,0 +1,193 @@ +using System.Net.Http; +using System.Threading.RateLimiting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Polly; +using Polly.CircuitBreaker; +using Polly.Registry; +using Polly.Retry; +using Polly.Timeout; +using static Snippets.Docs.Telemetry; + +namespace Snippets.Docs; + +internal static class DependencyInjection +{ + public static async Task AddResiliencePipeline() + { + #region add-resilience-pipeline + + var services = new ServiceCollection(); + + // Define a resilience pipeline + services.AddResiliencePipeline("my-key", builder => + { + // Add strategies to your pipeline here, timeout for example + builder.AddTimeout(TimeSpan.FromSeconds(10)); + }); + + // You can also access IServiceProvider by using the alternate overload + services.AddResiliencePipeline("my-key", (builder, context) => + { + // Resolve any service from DI + var loggerFactory = context.ServiceProvider.GetRequiredService(); + + // Add strategies to your pipeline here + builder.AddTimeout(TimeSpan.FromSeconds(10)); + }); + + // Resolve the resilience pipeline + ServiceProvider serviceProvider = services.BuildServiceProvider(); + ResiliencePipelineProvider pipelineProvider = serviceProvider.GetRequiredService>(); + ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-key"); + + // Use it + await pipeline.ExecuteAsync(async cancellation => await Task.Delay(100, cancellation)); + + #endregion + } + + public static async Task AddResiliencePipelineGeneric() + { + using var client = new HttpClient(); + var endpoint = new Uri("https://www.dummy.com"); + var cancellationToken = CancellationToken.None; + + #region add-resilience-pipeline-generic + + var services = new ServiceCollection(); + + // Define a generic resilience pipeline + // First parameter is the type of key, second one is the type of the results the generic pipeline works with + services.AddResiliencePipeline("my-pipeline", builder => + { + builder.AddRetry(new() + { + MaxRetryAttempts = 2, + ShouldHandle = new PredicateBuilder() + .Handle() + .Handle() + .HandleResult(response => response.StatusCode == System.Net.HttpStatusCode.InternalServerError) + }) + .AddTimeout(TimeSpan.FromSeconds(2)); + }); + + // Resolve the resilience pipeline + ServiceProvider serviceProvider = services.BuildServiceProvider(); + ResiliencePipelineProvider pipelineProvider = serviceProvider.GetRequiredService>(); + ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-key"); + + // Use it + await pipeline.ExecuteAsync( + async cancellation => await client.GetAsync(endpoint, cancellation), + cancellationToken); + + #endregion + } + + public static async Task DynamicReloads(IServiceCollection services, IConfigurationSection configurationSection) + { + #region di-dynamic-reloads + + services + .Configure("my-retry-options", configurationSection) // Configure the options + .AddResiliencePipeline("my-pipeline", (builder, context) => + { + // Enable the reloads whenever the named options change + context.EnableReloads("my-retry-options"); + + // Utility method to retrieve the named options + var retryOptions = context.GetOptions("my-retry-options"); + + // Add retries using the resolved options + builder.AddRetry(retryOptions); + }); + + #endregion + } + + public static async Task ResourceDisposal(IServiceCollection services) + { + #region di-resource-disposal + + services.AddResiliencePipeline("my-pipeline", (builder, context) => + { + // Create disposable resource + var limiter = new ConcurrencyLimiter(new ConcurrencyLimiterOptions { PermitLimit = 100, QueueLimit = 100 }); + + // Use it + builder.AddRateLimiter(limiter); + + // Dispose the resource created in the callback when the pipeline is discarded + context.OnPipelineDisposed(() => limiter.Dispose()); + }); + + #endregion + } + + #region di-registry-complex-key + + public record struct MyPipelineKey(string PipelineName, string InstanceName) + { + } + + #endregion + + public static async Task AddResiliencePipelineWithComplexKey(IServiceCollection services) + { + #region di-registry-add-pipeline + + services.AddResiliencePipeline(new MyPipelineKey("my-pipeline", string.Empty), builder => + { + // Circuit breaker is a stateful strategy. To isolate the builder across different pipelines, + // we must use multiple instances. + builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions()); + }); + + #endregion + } + + #region di-complex-key-comparer + + public sealed class PipelineNameComparer : IEqualityComparer + { + public bool Equals(MyPipelineKey x, MyPipelineKey y) => x.PipelineName == y.PipelineName; + + public int GetHashCode(MyPipelineKey obj) => (obj.PipelineName, obj.InstanceName).GetHashCode(); + } + + #endregion + + public static async Task ConfigureRegistry(IServiceCollection services) + { + #region di-registry-configure + + services + .AddResiliencePipelineRegistry(options => + { + options.BuilderComparer = new PipelineNameComparer(); + + options.InstanceNameFormatter = key => key.InstanceName; + + options.BuilderNameFormatter = key => key.PipelineName; + }); + + #endregion + } + + public static async Task ConfigureRegistry(IServiceProvider serviceProvider) + { + #region di-registry-multiple-instances + + ResiliencePipelineProvider pipelineProvider = serviceProvider.GetRequiredService>(); + + // The registry dynamically creates and caches instance-A using the associated builder action + ResiliencePipeline instanceA = pipelineProvider.GetPipeline(new MyPipelineKey("my-pipeline", "instance-A")); + + // The registry creates and caches instance-B + ResiliencePipeline instanceB = pipelineProvider.GetPipeline(new MyPipelineKey("my-pipeline", "instance-B")); + + #endregion + } +} diff --git a/src/Snippets/Docs/Telemetry.cs b/src/Snippets/Docs/Telemetry.cs index 17bc4dbabf2..afa079efedf 100644 --- a/src/Snippets/Docs/Telemetry.cs +++ b/src/Snippets/Docs/Telemetry.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Polly; using Polly.Retry; using Polly.Telemetry; @@ -48,6 +49,25 @@ public static void ConfigureTelemetry() #endregion } + public static void AddResiliencePipelineWithTelemetry() + { + #region add-resilience-pipeline-with-telemetry + + var serviceCollection = new ServiceCollection() + .AddLogging(builder => builder.AddConsole()) + .AddResiliencePipeline("my-strategy", builder => builder.AddTimeout(TimeSpan.FromSeconds(1))) + .Configure(options => + { + // Configure enrichers + options.MeteringEnrichers.Add(new MyMeteringEnricher()); + + // Configure telemetry listeners + options.TelemetryListeners.Add(new MyTelemetryListener()); + }); + + #endregion + } + #region telemetry-listeners internal class MyTelemetryListener : TelemetryListener diff --git a/src/Snippets/Extensions/Snippets.cs b/src/Snippets/Extensions/Snippets.cs deleted file mode 100644 index e0f0e420f92..00000000000 --- a/src/Snippets/Extensions/Snippets.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Polly; -using Polly.Registry; -using Polly.Telemetry; -using static Snippets.Docs.Telemetry; - -namespace Snippets.Extensions; - -internal static class Snippets -{ - public static async Task AddResiliencePipeline() - { - #region add-resilience-pipeline - - var services = new ServiceCollection(); - - // Define a resilience pipeline - services.AddResiliencePipeline( - "my-key", - builder => builder.AddTimeout(TimeSpan.FromSeconds(10))); - - // Define a resilience pipeline with custom options - services - .Configure(options => options.Timeout = TimeSpan.FromSeconds(10)) - .AddResiliencePipeline( - "my-timeout", - (builder, context) => - { - var myOptions = context.GetOptions(); - - builder.AddTimeout(myOptions.Timeout); - }); - - // Resolve the resilience pipeline - var serviceProvider = services.BuildServiceProvider(); - var pipelineProvider = serviceProvider.GetRequiredService>(); - var pipeline = pipelineProvider.GetPipeline("my-key"); - - // Use it - await pipeline.ExecuteAsync(async cancellation => await Task.Delay(100, cancellation)); - - #endregion - } - - public static void AddResiliencePipelineWithTelemetry() - { - #region add-resilience-pipeline-with-telemetry - - var serviceCollection = new ServiceCollection() - .AddLogging(builder => builder.AddConsole()) - .AddResiliencePipeline("my-strategy", builder => builder.AddTimeout(TimeSpan.FromSeconds(1))) - .Configure(options => - { - // Configure enrichers - options.MeteringEnrichers.Add(new MyMeteringEnricher()); - - // Configure telemetry listeners - options.TelemetryListeners.Add(new MyTelemetryListener()); - }); - - #endregion - } - - private class MyTimeoutOptions - { - public TimeSpan Timeout { get; set; } - } -} From 63e621502515f29359bc251872eaedd0a8363ff7 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 7 Sep 2023 13:29:26 +0200 Subject: [PATCH 2/3] PR comments --- docs/dependency-injection.md | 12 ++++++------ src/Polly.Extensions/README.md | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/dependency-injection.md b/docs/dependency-injection.md index e68e5bef052..3a8969dbae6 100644 --- a/docs/dependency-injection.md +++ b/docs/dependency-injection.md @@ -1,6 +1,6 @@ # Dependency injection -Starting with version 8, Polly provides features that make the integration of Polly with the standard [`IServiceCollection`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection) Dependency Injection (DI) container more streamlined. This is a thin layer atop the [resilience pipeline registry](resilience-pipeline-registry.md) which manages resilience pipelines. +Starting with version 8, Polly provides features that make the integration of Polly with the .NET [`IServiceCollection`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection) Dependency Injection (DI) container more streamlined. This is a thin layer atop the [resilience pipeline registry](resilience-pipeline-registry.md) which manages resilience pipelines. ## Usage @@ -90,7 +90,7 @@ await pipeline.ExecuteAsync( ## Dynamic reloads -The dynamic reloads is a feature of pipeline registry that is also surfaced when using the `AddResiliencePipeline(...)` extension method. Use an overload that provides access to `AddResiliencePipelineContext`: +Dynamic reloading is a feature of the pipeline registry that is also surfaced when using the `AddResiliencePipeline(...)` extension method. Use an overload that provides access to `AddResiliencePipelineContext`: ```cs @@ -110,7 +110,7 @@ services ``` -- `EnableReloads` activates the dynamic reloading of `my-pipeline`. +- `EnableReloads(...)` activates the dynamic reloading of `my-pipeline`. - `RetryStrategyOptions` are fetched using `context.GetOptions(...)` utility method. - A retry strategy is added. @@ -123,7 +123,7 @@ If an error occurs during reloading, the old pipeline remains, and dynamic reloa ## Resource disposal -Like dynamic reloads, the pipeline registry's resource disposal feature lets you register callbacks. These callbacks run when the pipeline is discarded, reloaded, or the registry is disposed at application shutdown. +Like dynamic reloading, the pipeline registry's resource disposal feature lets you register callbacks. These callbacks run when the pipeline is discarded, reloaded, or the registry is disposed at application shutdown. See the example below: @@ -172,7 +172,7 @@ services.AddResiliencePipeline(new MyPipelineKey("my-pipeline", string.Empty), b ``` -The "my-pipeline" is now registered. Note that the `InstanceName` is an empty string. While we're registering the builder action for a specific pipeline, the `InstanceName` parameter isn't used during the pipeline's registration. Some further modifications are required for this to function. +The "my-pipeline" pipeline is now registered. Note that the `InstanceName` is an empty string. While we're registering the builder action for a specific pipeline, the `InstanceName` parameter isn't used during the pipeline's registration. Some further modifications are required for this to function. Introduce the `PipelineNameComparer`: @@ -209,7 +209,7 @@ Let's summarize our actions: - We used the `InstanceNameFormatter` delegate to represent the `MyPipelineKey` as an instance name for telemetry purposes, keeping the instance name as it is. - Likewise, the `BuilderNameFormatter` delegate represents the `MyPipelineKey` as a builder name in telemetry. -Finally, utilize the `ResiliencePipelineProvider` to dynamically create and cache multiple instances of the same pipeline: +Finally, use the `ResiliencePipelineProvider` to dynamically create and cache multiple instances of the same pipeline: ```cs diff --git a/src/Polly.Extensions/README.md b/src/Polly.Extensions/README.md index 19499c8af96..30c6d0ce266 100644 --- a/src/Polly.Extensions/README.md +++ b/src/Polly.Extensions/README.md @@ -1,4 +1,4 @@ -# Polly.extensions overview +# Polly.Extensions overview This project provides the following features: From c30d452dd8f65ccd98bd1edc2c99f7f75094a1df Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 7 Sep 2023 13:30:23 +0200 Subject: [PATCH 3/3] fixes --- docs/strategies/hedging.md | 2 +- src/Snippets/Docs/Hedging.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/strategies/hedging.md b/docs/strategies/hedging.md index e91eccf747a..fb62d06216f 100644 --- a/docs/strategies/hedging.md +++ b/docs/strategies/hedging.md @@ -171,7 +171,7 @@ new ResiliencePipelineBuilder() { try { - // A dedicated ActionContext is provided for each hedged action + // A dedicated ActionContext is provided for each hedged action. // It comes with a separate CancellationToken created specifically for this hedged attempt, // which can be cancelled later if needed. // diff --git a/src/Snippets/Docs/Hedging.cs b/src/Snippets/Docs/Hedging.cs index 2912e392471..b57bd303c9c 100644 --- a/src/Snippets/Docs/Hedging.cs +++ b/src/Snippets/Docs/Hedging.cs @@ -101,7 +101,7 @@ public static void ActionGenerator() { try { - // A dedicated ActionContext is provided for each hedged action + // A dedicated ActionContext is provided for each hedged action. // It comes with a separate CancellationToken created specifically for this hedged attempt, // which can be cancelled later if needed. //