diff --git a/README_V8.md b/README_V8.md index c6e169ac407..b2f5a5abd4a 100644 --- a/README_V8.md +++ b/README_V8.md @@ -125,7 +125,7 @@ Visit [resilience strategies](docs/resilience-strategies.md) docs to explore how ```cs // Add retry using the default options. -// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/retry.md#defaults for default values. +// See https://www.pollydocs.org/strategies/retry#defaults for defaults. new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions()); // For instant retries with no delay @@ -208,7 +208,7 @@ If all retries fail, a retry strategy rethrows the final exception back to the c ```cs // Add circuit breaker with default options. -// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/circuit-breaker.md#defaults for default values. +// See https://www.pollydocs.org/strategies/circuit-breaker#defaults for defaults. new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions()); // Add circuit breaker with customized options: @@ -320,7 +320,7 @@ For more details, refer to the [Fallback documentation](docs/strategies/fallback ```cs // Add hedging with default options. -// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/hedging.md#defaults for default values. +// See https://www.pollydocs.org/strategies/hedging#defaults for defaults. new ResiliencePipelineBuilder() .AddHedging(new HedgingStrategyOptions()); @@ -366,7 +366,7 @@ The timeout resilience strategy assumes delegates you execute support [co-operat ```cs // Add timeout using the default options. -// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/timeout.md#defaults for default values. +// See https://www.pollydocs.org/strategies/timeout#defaults for defaults. new ResiliencePipelineBuilder() .AddTimeout(new TimeoutStrategyOptions()); @@ -410,7 +410,7 @@ Timeout strategies throw `TimeoutRejectedException` when a timeout occurs. For m ```cs // Add rate limiter with default options. -// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/rate-limiter.md#defaults for default values. +// See https://www.pollydocs.org/strategies/rate-limiter#defaults for defaults. new ResiliencePipelineBuilder() .AddRateLimiter(new RateLimiterStrategyOptions()); diff --git a/docs/advanced/resilience-context.md b/docs/advanced/resilience-context.md index dd9bf747df6..7f5e412f903 100644 --- a/docs/advanced/resilience-context.md +++ b/docs/advanced/resilience-context.md @@ -22,7 +22,6 @@ Below is an example demonstrating how to work with `ResilienceContext`: ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken); // Attach custom data to the context - context.Properties.Set(MyResilienceKeys.Key1, "my-data"); context.Properties.Set(MyResilienceKeys.Key2, 123); @@ -69,6 +68,9 @@ public static class MyResilienceKeys ``` +> [!NOTE] +> We recommend defining a static class to hold the resilience property keys used in your project. This approach makes these keys easier to discover and maintain. For simpler scenarios, you can directly use the creation of `ResiliencePropertyKey` since it's a cheap, struct-based API. + ## Resilient context pooling diff --git a/docs/migration-v8.md b/docs/migration-v8.md index 5be94d682ba..c8188094a09 100644 --- a/docs/migration-v8.md +++ b/docs/migration-v8.md @@ -1 +1,482 @@ # Migration guide from v7 to v8 + +Welcome to the migration guide for Polly's v8 version. The v8 version of Polly brings major new enhancements and supports all the same scenarios as previous versions. In the following sections, we'll detail the differences between the v7 and v8 APIs and provide steps on how to transition smoothly. + +> [!NOTE] +> The v7 API is still available and fully supported when using the v8 version. + +## Major differences + +- **The term *Policy* is now replaced with *Strategy***: In previous versions, Polly used the term *policy* for retries, timeouts, etc. In v8, these are referred to as *resilience strategies*. +- **Introduction of Resilience Pipelines**: The [resilience pipeline](pipelines/index.md) combines one or more resilience strategies. This is the foundational API for Polly v8, similar to the **Policy Wrap** in previous versions but integrated into the core API. +- **Unification of interfaces**: Interfaces such as `IAsyncPolicy`, `IAsyncPolicy`, `ISyncPolicy`, `ISyncPolicy`, and `IPolicy` are now unified under `ResiliencePipeline` and `ResiliencePipeline`. The resilience pipeline supports both synchronous and asynchronous executions. +- **Native async support**: Polly v8 was designed with asynchronous support from the start. +- **No static APIs**: Unlike previous versions, v8 doesn't use static APIs. This improves testability and extensibility while maintaining ease of use. +- **Options-based configuration**: Configuring individual resilience strategies is now options-based, offering more flexibility and improving maintainability and extensibility. +- **Built-in telemetry**: Polly v8 now has built-in telemetry support. +- **Improved performance and low-allocation APIs**: Polly v8 boasts significant performance enhancements and provides zero-allocation APIs for advanced use cases. + +## Migrating policies + +In earlier versions, Polly exposed various interfaces to execute user code: + +- `IAsyncPolicy` +- `IAsyncPolicy` +- `ISyncPolicy` +- `ISyncPolicy` + +### Configuring policies in v7 + +These interfaces were created and used as: + + +```cs +// Create and use the ISyncPolicy +ISyncPolicy syncPolicy = Policy.Handle().WaitAndRetry(3, _ => TimeSpan.FromSeconds(1)); +syncPolicy.Execute(() => +{ + // your code here +}); + +// Create and use the IAsyncPolicy +IAsyncPolicy asyncPolicy = Policy.Handle().WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1)); +await asyncPolicy.ExecuteAsync( + async cancellationToken => + { + // your code here + }, + cancellationToken); + +// Create and use the ISyncPolicy +ISyncPolicy syncPolicyT = Policy + .HandleResult(r => !r.IsSuccessStatusCode) + .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1)); + +syncPolicyT.Execute(() => +{ + // your code here + return GetResponse(); +}); + +// Create and use the IAsyncPolicy +IAsyncPolicy asyncPolicyT = Policy + .HandleResult(r => !r.IsSuccessStatusCode) + .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1)); +await asyncPolicyT.ExecuteAsync( + async cancellationToken => + { + // your code here + return await GetResponseAsync(cancellationToken); + }, + cancellationToken); +``` + + +Points to note from the example: + +- `Policy.Handle()` determines which exceptions are handled. +- `Policy.HandleResult()` determines which results are managed. +- The final `WaitAndRetry()` produces an `ISyncPolicy`, while `WaitAndRetryAsync()` results in an `IAsyncPolicy`. +- The retry policy tries 3 times, waiting 1 second between attempts. + +### Configuring strategies in v8 + +In Polly v8, the previous code becomes: + + +```cs +// Create and use the ResiliencePipeline +ResiliencePipeline pipeline = new ResiliencePipelineBuilder() + .AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder().Handle(), + Delay = TimeSpan.FromSeconds(1), + MaxRetryAttempts = 3, + BackoffType = DelayBackoffType.Constant + }) + .Build(); + +// Synchronous execution +pipeline.Execute(() => +{ + // your code here +}); + +// Asynchronous execution +await pipeline.ExecuteAsync(async cancellationToken => +{ + // your code here +}, +cancellationToken); + +// Create and use the ResiliencePipeline +ResiliencePipeline pipelineT = new ResiliencePipelineBuilder() + .AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder() + .Handle() + .HandleResult(result => !result.IsSuccessStatusCode), + Delay = TimeSpan.FromSeconds(1), + MaxRetryAttempts = 3, + BackoffType = DelayBackoffType.Constant + }) + .Build(); + +// Synchronous execution +pipelineT.Execute(() => +{ + // your code here + return GetResponse(); +}); + +// Asynchronous execution +await pipelineT.ExecuteAsync(async cancellationToken => +{ + // your code here + return await GetResponseAsync(cancellationToken); +}, +cancellationToken); +``` + + +Here are the primary changes: + +- `ResiliencePipelineBuilder` is used to start building the resilience pipeline, instead of the static `Policy.HandleException(...)`. +- For building generic resilience pipelines, `ResiliencePipelineBuilder` replaces the static `Policy.handleResult(...)`. +- Resilience strategies are configured using options, such as `RetryStrategyOptions` and `RetryStrategyOptions`. +- `PredicateBuilder` determines which exceptions or results are handled. +- The final `ResiliencePipeline` is created with a `Build()` call. +- `ResiliencePipeline` can execute both synchronous and asynchronous tasks. + +## Migrating `PolicyWrap` + +`PolicyWrap` is used to combine multiple policies into one as shown below: + + +```cs +IAsyncPolicy retryPolicy = Policy.Handle().WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1)); + +IAsyncPolicy timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(3)); + +// Wrap the policies. Tne policies are executed in the following order: +// 1. Retry +// 2. Timeout +IAsyncPolicy wrappedPolicy = Policy.WrapAsync(timeoutPolicy, retryPolicy); +``` + + +In v8, there's no need to use `PolicyWrap` explicitly. Instead, policy wrapping is smoothly integrated into `ResiliencePipelineBuilder`, as shown in the example below: + + +```cs +// The "PolicyWrap" is integrated directly. Strategies are executed in the same order as they were added: +// 1. Retry +// 2. Timeout +ResiliencePipeline pipeline = new ResiliencePipelineBuilder() + .AddRetry(new() + { + MaxRetryAttempts = 3, + Delay = TimeSpan.FromSeconds(1), + BackoffType = DelayBackoffType.Constant, + ShouldHandle = new PredicateBuilder().Handle() + }) + .AddTimeout(TimeSpan.FromSeconds(3)) + .Build(); +``` + + +> [!IMPORTANT] +> In v7, the `PolicyWrap` ordering was different; the policy added first was executed last. In v8, the execution order matches the order in which they were added. + +## Migrating retry policy + +We will only include the retry policy as migration showcase. Migrating other policies uses the same approach. + +### Retries in v7 + + +```cs +// Retry once +Policy + .Handle() + .Retry(); + +// Retry multiple times +Policy + .Handle() + .Retry(3); + +// Retry multiple times with callback +Policy + .Handle() + .Retry(3, onRetry: (exception, retryCount) => + { + // Add logic to be executed before each retry, such as logging + }); + +// Retry forever +Policy + .Handle() + .WaitAndRetryForever(_ => TimeSpan.FromSeconds(1)); + +// Wait and retry multiple times +Policy + .Handle() + .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1)); + +// Wait and retry multiple times with callback +Policy + .Handle() + .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1), onRetry: (exception, retryCount) => + { + // Add logic to be executed before each retry, such as logging + }); + +// Wait and retry forever +Policy + .Handle() + .WaitAndRetryForever(_ => TimeSpan.FromSeconds(1)); +``` + + +### Retries in v8 + + +```cs +// Retry once +new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions +{ + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = 1, + Delay = TimeSpan.Zero, +}) +.Build(); + +// Retry multiple times +new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions +{ + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = 3, + Delay = TimeSpan.Zero, +}) +.Build(); + +// Retry multiple times with callback +new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions +{ + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = 3, + Delay = TimeSpan.Zero, + OnRetry = args => + { + // Add logic to be executed before each retry, such as logging + return default; + } +}) +.Build(); + +// Retry forever +new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions +{ + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = int.MaxValue, + Delay = TimeSpan.Zero, +}) +.Build(); + +// Wait and retry multiple times +new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions +{ + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = 3, + Delay = TimeSpan.FromSeconds(1), + BackoffType = DelayBackoffType.Constant +}) +.Build(); + +// Wait and retry multiple times with callback +new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions +{ + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = 3, + Delay = TimeSpan.FromSeconds(1), + BackoffType = DelayBackoffType.Constant, + OnRetry = args => + { + // Add logic to be executed before each retry, such as logging + return default; + } +}) +.Build(); + +// Wait and retry forever +new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions +{ + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = int.MaxValue, + Delay = TimeSpan.FromSeconds(1), + BackoffType = DelayBackoffType.Constant +}) +.Build(); +``` + + +- It's important to remember that the configuration in v8 is options based, i.e. `RetryStrategyOptions` are used. +- Notice that to disable waiting between retries the `Delay` property is set to `TimeSpan.Zero`. +- To retry forever set `MaxRetryAttempts` to `int.MaxValue`. + +## Migrating bulkhead policy + +The bulkhead policy is now replaced by the [rate limiter strategy](strategies/rate-limiter.md) which uses the [`System.Threading.RateLimiting`](https://www.nuget.org/packages/System.Threading.RateLimiting) package. The new counterpart to bulkhead is `ConcurrencyLimiter`. + +### Bulkhead in v7 + + +```cs +Policy.Bulkhead(maxParallelization: 100, maxQueuingActions: 50); + +Policy.BulkheadAsync(maxParallelization: 100, maxQueuingActions: 50); + +Policy.Bulkhead(maxParallelization: 100, maxQueuingActions: 50); + +Policy.BulkheadAsync(maxParallelization: 100, maxQueuingActions: 50); +``` + + +### Concurrency limiter in v8 + + +```cs +new ResiliencePipelineBuilder().AddConcurrencyLimiter(permitLimit: 100, queueLimit: 50).Build(); + +new ResiliencePipelineBuilder().AddConcurrencyLimiter(permitLimit: 100, queueLimit: 50).Build(); +``` + + +## Migrating `Polly.Context` + +`Polly.Context` has been succeeded by `ResilienceContext`. Here are the main changes: + +- `ResilienceContext` is pooled for enhanced performance and isn't directly creatable. Instead, use the `ResilienceContextPool` to get an instance. +- Directly attaching custom data is supported by `Context`, whereas `ResilienceContext` employs the `ResilienceContext.Properties` property. +- Both `PolicyKey` and `PolicyWrapKey` are no longer a part of `ResilienceContext`. They've been relocated to `ResiliencePipelineBuilder` and are now used for [telemetry](advanced/telemetry.md#metrics). +- The `CorrelationId` property has been removed. For similar functionality, you can either use `System.Diagnostics.Activity.Current.Id` or attach your custom id using `ResilienceContext.Properties`. +- Additionally, `ResilienceContext` introduces the `CancellationToken` property. + +### `Context` in v7 + + +```cs +// Create context +Context context = new Context(); + +// Create context with operation key +context = new Context("my-operation-key"); + +// Attach custom properties +context["prop-1"] = "value-1"; +context["prop-2"] = 100; + +// Retrieve custom properties +string value1 = (string)context["prop-1"]; +int value2 = (int)context["prop-2"]; +``` + + +### `ResilienceContext` in v8 + + +```cs +// Create context +ResilienceContext context = ResilienceContextPool.Shared.Get(); + +// Create context with operation key +context = ResilienceContextPool.Shared.Get("my-operation-key"); + +// Attach custom properties +context.Properties.Set(new ResiliencePropertyKey("prop-1"), "value-1"); +context.Properties.Set(new ResiliencePropertyKey("prop-2"), 100); + +// Retrieve custom properties +string value1 = context.Properties.GetValue(new ResiliencePropertyKey("prop-1"), "default"); +int value2 = context.Properties.GetValue(new ResiliencePropertyKey("prop-2"), 0); + +// Return the context to the pool +ResilienceContextPool.Shared.Return(context); +``` + + +For more details, refer to the [Resilience context](advanced/resilience-context.md) documentation. + +## Migrating no-op policy + +- For `Policy.NoOp` or `Policy.NoOpAsync`, now use `ResiliencePipeline.Empty`. +- For `Policy.NoOp` or `Policy.NoOpAsync`, switch to `ResiliencePipeline.Empty`. + +## Migrating policy registry + +In v7, the following registry APIs are exposed: + +- `IPolicyRegistry` +- `IReadOnlyPolicyRegistry` +- `IConcurrentPolicyRegistry` +- `PolicyRegistry` + +In v8, these have been replaced by: + +- `ResiliencePipelineProvider` +- `ResiliencePipelineRegistry` + +The main updates in the new registry include: + +- It's append-only, which means removal of items is not supported to avoid race conditions. +- It's thread-safe and supports features like dynamic reloads and resource disposal. +- It allows dynamic creation and caching of resilience pipelines (previously known as policies in v7) using pre-registered delegates. +- Type safety is enhanced, eliminating the need for casting between policy types. + +For more details, refer to the [pipeline registry](pipelines/resilience-pipeline-registry.md) documentation. + +### Registry in v7 + + +```cs +// Create a registry +var registry = new PolicyRegistry(); + +// Try get a policy +registry.TryGet("my-key", out IAsyncPolicy? policy); + +// Try get a generic policy +registry.TryGet>("my-key", out IAsyncPolicy? genericPolicy); + +// Add a policy +registry.Add("my-key", Policy.Timeout(TimeSpan.FromSeconds(10))); + +// Update a policy +registry.AddOrUpdate( + "my-key", + Policy.Timeout(TimeSpan.FromSeconds(10)), + (key, old) => Policy.Timeout(TimeSpan.FromSeconds(10))); +``` + + +### Registry in v8 + + +```cs +// Create a registry +var registry = new ResiliencePipelineRegistry(); + +// Try get a pipeline +registry.TryGetPipeline("my-key", out ResiliencePipeline? pipeline); + +// Try get a generic pipeline +registry.TryGetPipeline("my-key", out ResiliencePipeline? genericPipeline); + +// Add a pipeline using a builder, when "my-key" pipeline is retrieved it will be dynamically built and cached +registry.TryAddBuilder("my-key", (builder, context) => builder.AddTimeout(TimeSpan.FromSeconds(10))); + +// Get or add pipeline +registry.GetOrAddPipeline("my-key", builder => builder.AddTimeout(TimeSpan.FromSeconds(10))); +``` + diff --git a/docs/strategies/circuit-breaker.md b/docs/strategies/circuit-breaker.md index 79b6f050cd5..becdaa15a70 100644 --- a/docs/strategies/circuit-breaker.md +++ b/docs/strategies/circuit-breaker.md @@ -24,7 +24,7 @@ ```cs // Add circuit breaker with default options. -// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/circuit-breaker.md#defaults for default values. +// See https://www.pollydocs.org/strategies/circuit-breaker#defaults for defaults. new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions()); // Add circuit breaker with customized options: diff --git a/docs/strategies/hedging.md b/docs/strategies/hedging.md index 62cee851cdb..edf6c95c2fe 100644 --- a/docs/strategies/hedging.md +++ b/docs/strategies/hedging.md @@ -20,7 +20,7 @@ This strategy also supports multiple [concurrency modes](#concurrency-modes) for ```cs // Add hedging with default options. -// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/hedging.md#defaults for default values. +// See https://www.pollydocs.org/strategies/hedging#defaults for defaults. new ResiliencePipelineBuilder() .AddHedging(new HedgingStrategyOptions()); diff --git a/docs/strategies/rate-limiter.md b/docs/strategies/rate-limiter.md index 06948960888..0c8d77618b7 100644 --- a/docs/strategies/rate-limiter.md +++ b/docs/strategies/rate-limiter.md @@ -23,7 +23,7 @@ Further reading: ```cs // Add rate limiter with default options. -// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/rate-limiter.md#defaults for default values. +// See https://www.pollydocs.org/strategies/rate-limiter#defaults for defaults. new ResiliencePipelineBuilder() .AddRateLimiter(new RateLimiterStrategyOptions()); diff --git a/docs/strategies/retry.md b/docs/strategies/retry.md index bec3d11a9af..65f29ffddff 100644 --- a/docs/strategies/retry.md +++ b/docs/strategies/retry.md @@ -18,7 +18,7 @@ ```cs // Add retry using the default options. -// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/retry.md#defaults for default values. +// See https://www.pollydocs.org/strategies/retry#defaults for defaults. new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions()); // For instant retries with no delay diff --git a/docs/strategies/timeout.md b/docs/strategies/timeout.md index ba75a1f81cc..b4ad7315ea5 100644 --- a/docs/strategies/timeout.md +++ b/docs/strategies/timeout.md @@ -18,7 +18,7 @@ ```cs // Add timeout using the default options. -// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/timeout.md#defaults for default values. +// See https://www.pollydocs.org/strategies/timeout#defaults for defaults. new ResiliencePipelineBuilder() .AddTimeout(new TimeoutStrategyOptions()); diff --git a/src/Snippets/Core/Snippets.cs b/src/Snippets/Core/Snippets.cs index 526835b52b6..92d6dc0d542 100644 --- a/src/Snippets/Core/Snippets.cs +++ b/src/Snippets/Core/Snippets.cs @@ -1,5 +1,4 @@ -using Polly; -using Polly.Retry; +using Polly.Retry; namespace Snippets.Core; diff --git a/src/Snippets/Docs/CircuitBreaker.cs b/src/Snippets/Docs/CircuitBreaker.cs index 568d8bdafcc..0d9b74517d4 100644 --- a/src/Snippets/Docs/CircuitBreaker.cs +++ b/src/Snippets/Docs/CircuitBreaker.cs @@ -1,6 +1,5 @@ using System.Net; using System.Net.Http; -using Polly; using Polly.CircuitBreaker; using Snippets.Docs.Utils; @@ -13,7 +12,7 @@ public static async Task Usage() #region circuit-breaker // Add circuit breaker with default options. - // See https://github.com/App-vNext/Polly/blob/main/docs/strategies/circuit-breaker.md#defaults for default values. + // See https://www.pollydocs.org/strategies/circuit-breaker#defaults for defaults. new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions()); // Add circuit breaker with customized options: diff --git a/src/Snippets/Docs/DependencyInjection.cs b/src/Snippets/Docs/DependencyInjection.cs index eed00eb3223..cd42a730b11 100644 --- a/src/Snippets/Docs/DependencyInjection.cs +++ b/src/Snippets/Docs/DependencyInjection.cs @@ -3,12 +3,10 @@ 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; diff --git a/src/Snippets/Docs/Fallback.cs b/src/Snippets/Docs/Fallback.cs index cf9a6706462..3e7e0d3b4b5 100644 --- a/src/Snippets/Docs/Fallback.cs +++ b/src/Snippets/Docs/Fallback.cs @@ -1,5 +1,4 @@ -using Polly; -using Polly.Fallback; +using Polly.Fallback; using Snippets.Docs.Utils; namespace Snippets.Docs; diff --git a/src/Snippets/Docs/General.cs b/src/Snippets/Docs/General.cs index 3880cc6ba1c..4b12a9daa62 100644 --- a/src/Snippets/Docs/General.cs +++ b/src/Snippets/Docs/General.cs @@ -1,6 +1,4 @@ -using Polly; - -namespace Snippets.Docs; +namespace Snippets.Docs; internal static class General { diff --git a/src/Snippets/Docs/Hedging.cs b/src/Snippets/Docs/Hedging.cs index 41f5d885a26..40f59d0f91a 100644 --- a/src/Snippets/Docs/Hedging.cs +++ b/src/Snippets/Docs/Hedging.cs @@ -1,6 +1,5 @@ using System.Net; using System.Net.Http; -using Polly; using Polly.Hedging; using Snippets.Docs.Utils; @@ -15,7 +14,7 @@ public static void Usage() #region hedging // Add hedging with default options. - // See https://github.com/App-vNext/Polly/blob/main/docs/strategies/hedging.md#defaults for default values. + // See https://www.pollydocs.org/strategies/hedging#defaults for defaults. new ResiliencePipelineBuilder() .AddHedging(new HedgingStrategyOptions()); diff --git a/src/Snippets/Docs/Migration.Bulkhead.cs b/src/Snippets/Docs/Migration.Bulkhead.cs new file mode 100644 index 00000000000..c19cd80fae2 --- /dev/null +++ b/src/Snippets/Docs/Migration.Bulkhead.cs @@ -0,0 +1,32 @@ +using System.Net.Http; + +namespace Snippets.Docs; + +internal static partial class Migration +{ + public static void Bulkhead_V7() + { + #region migration-bulkhead-v7 + + Policy.Bulkhead(maxParallelization: 100, maxQueuingActions: 50); + + Policy.BulkheadAsync(maxParallelization: 100, maxQueuingActions: 50); + + Policy.Bulkhead(maxParallelization: 100, maxQueuingActions: 50); + + Policy.BulkheadAsync(maxParallelization: 100, maxQueuingActions: 50); + + #endregion + } + + public static void Bulkhead_V8() + { + #region migration-bulkhead-v8 + + new ResiliencePipelineBuilder().AddConcurrencyLimiter(permitLimit: 100, queueLimit: 50).Build(); + + new ResiliencePipelineBuilder().AddConcurrencyLimiter(permitLimit: 100, queueLimit: 50).Build(); + + #endregion + } +} diff --git a/src/Snippets/Docs/Migration.Context.cs b/src/Snippets/Docs/Migration.Context.cs new file mode 100644 index 00000000000..f07beb7974d --- /dev/null +++ b/src/Snippets/Docs/Migration.Context.cs @@ -0,0 +1,49 @@ +namespace Snippets.Docs; + +internal static partial class Migration +{ + public static void Context_V7() + { + #region migration-context-v7 + + // Create context + Context context = new Context(); + + // Create context with operation key + context = new Context("my-operation-key"); + + // Attach custom properties + context["prop-1"] = "value-1"; + context["prop-2"] = 100; + + // Retrieve custom properties + string value1 = (string)context["prop-1"]; + int value2 = (int)context["prop-2"]; + + #endregion + } + + public static void Context_V8() + { + #region migration-context-v8 + + // Create context + ResilienceContext context = ResilienceContextPool.Shared.Get(); + + // Create context with operation key + context = ResilienceContextPool.Shared.Get("my-operation-key"); + + // Attach custom properties + context.Properties.Set(new ResiliencePropertyKey("prop-1"), "value-1"); + context.Properties.Set(new ResiliencePropertyKey("prop-2"), 100); + + // Retrieve custom properties + string value1 = context.Properties.GetValue(new ResiliencePropertyKey("prop-1"), "default"); + int value2 = context.Properties.GetValue(new ResiliencePropertyKey("prop-2"), 0); + + // Return the context to the pool + ResilienceContextPool.Shared.Return(context); + + #endregion + } +} diff --git a/src/Snippets/Docs/Migration.Policies.cs b/src/Snippets/Docs/Migration.Policies.cs new file mode 100644 index 00000000000..6883aca2d2f --- /dev/null +++ b/src/Snippets/Docs/Migration.Policies.cs @@ -0,0 +1,120 @@ +using System.Net.Http; +using Polly.Retry; + +namespace Snippets.Docs; + +internal static partial class Migration +{ + public static async Task Policies() + { + var cancellationToken = CancellationToken.None; + + #region migration-policies-v7 + + // Create and use the ISyncPolicy + ISyncPolicy syncPolicy = Policy.Handle().WaitAndRetry(3, _ => TimeSpan.FromSeconds(1)); + syncPolicy.Execute(() => + { + // your code here + }); + + // Create and use the IAsyncPolicy + IAsyncPolicy asyncPolicy = Policy.Handle().WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1)); + await asyncPolicy.ExecuteAsync( + async cancellationToken => + { + // your code here + }, + cancellationToken); + + // Create and use the ISyncPolicy + ISyncPolicy syncPolicyT = Policy + .HandleResult(r => !r.IsSuccessStatusCode) + .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1)); + + syncPolicyT.Execute(() => + { + // your code here + return GetResponse(); + }); + + // Create and use the IAsyncPolicy + IAsyncPolicy asyncPolicyT = Policy + .HandleResult(r => !r.IsSuccessStatusCode) + .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1)); + await asyncPolicyT.ExecuteAsync( + async cancellationToken => + { + // your code here + return await GetResponseAsync(cancellationToken); + }, + cancellationToken); + + #endregion + } + + public static async Task Strategies() + { + var cancellationToken = CancellationToken.None; + + #region migration-policies-v8 + + // Create and use the ResiliencePipeline + ResiliencePipeline pipeline = new ResiliencePipelineBuilder() + .AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder().Handle(), + Delay = TimeSpan.FromSeconds(1), + MaxRetryAttempts = 3, + BackoffType = DelayBackoffType.Constant + }) + .Build(); + + // Synchronous execution + pipeline.Execute(() => + { + // your code here + }); + + // Asynchronous execution + await pipeline.ExecuteAsync(async cancellationToken => + { + // your code here + }, + cancellationToken); + + // Create and use the ResiliencePipeline + ResiliencePipeline pipelineT = new ResiliencePipelineBuilder() + .AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder() + .Handle() + .HandleResult(result => !result.IsSuccessStatusCode), + Delay = TimeSpan.FromSeconds(1), + MaxRetryAttempts = 3, + BackoffType = DelayBackoffType.Constant + }) + .Build(); + + // Synchronous execution + pipelineT.Execute(() => + { + // your code here + return GetResponse(); + }); + + // Asynchronous execution + await pipelineT.ExecuteAsync(async cancellationToken => + { + // your code here + return await GetResponseAsync(cancellationToken); + }, + cancellationToken); + + #endregion + } + + private static HttpResponseMessage GetResponse() => new(); + + private static Task GetResponseAsync(CancellationToken cancellationToken) => Task.FromResult(new HttpResponseMessage()); +} diff --git a/src/Snippets/Docs/Migration.Registry.cs b/src/Snippets/Docs/Migration.Registry.cs new file mode 100644 index 00000000000..45f49fb824e --- /dev/null +++ b/src/Snippets/Docs/Migration.Registry.cs @@ -0,0 +1,53 @@ +using Polly.Registry; + +namespace Snippets.Docs; + +internal static partial class Migration +{ + public static void Registry_V7() + { + #region migration-registry-v7 + + // Create a registry + var registry = new PolicyRegistry(); + + // Try get a policy + registry.TryGet("my-key", out IAsyncPolicy? policy); + + // Try get a generic policy + registry.TryGet>("my-key", out IAsyncPolicy? genericPolicy); + + // Add a policy + registry.Add("my-key", Policy.Timeout(TimeSpan.FromSeconds(10))); + + // Update a policy + registry.AddOrUpdate( + "my-key", + Policy.Timeout(TimeSpan.FromSeconds(10)), + (key, old) => Policy.Timeout(TimeSpan.FromSeconds(10))); + + #endregion + } + + public static void Registry_V8() + { + #region migration-registry-v8 + + // Create a registry + var registry = new ResiliencePipelineRegistry(); + + // Try get a pipeline + registry.TryGetPipeline("my-key", out ResiliencePipeline? pipeline); + + // Try get a generic pipeline + registry.TryGetPipeline("my-key", out ResiliencePipeline? genericPipeline); + + // Add a pipeline using a builder, when "my-key" pipeline is retrieved it will be dynamically built and cached + registry.TryAddBuilder("my-key", (builder, context) => builder.AddTimeout(TimeSpan.FromSeconds(10))); + + // Get or add pipeline + registry.GetOrAddPipeline("my-key", builder => builder.AddTimeout(TimeSpan.FromSeconds(10))); + + #endregion + } +} diff --git a/src/Snippets/Docs/Migration.Retry.cs b/src/Snippets/Docs/Migration.Retry.cs new file mode 100644 index 00000000000..80295f4a1d7 --- /dev/null +++ b/src/Snippets/Docs/Migration.Retry.cs @@ -0,0 +1,138 @@ +using Polly.Retry; +using Snippets.Docs.Utils; + +namespace Snippets.Docs; + +internal static partial class Migration +{ + public static void Retry_V7() + { + #region migration-retry-v7 + + // Retry once + Policy + .Handle() + .Retry(); + + // Retry multiple times + Policy + .Handle() + .Retry(3); + + // Retry multiple times with callback + Policy + .Handle() + .Retry(3, onRetry: (exception, retryCount) => + { + // Add logic to be executed before each retry, such as logging + }); + + // Retry forever + Policy + .Handle() + .WaitAndRetryForever(_ => TimeSpan.FromSeconds(1)); + + // Wait and retry multiple times + Policy + .Handle() + .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1)); + + // Wait and retry multiple times with callback + Policy + .Handle() + .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1), onRetry: (exception, retryCount) => + { + // Add logic to be executed before each retry, such as logging + }); + + // Wait and retry forever + Policy + .Handle() + .WaitAndRetryForever(_ => TimeSpan.FromSeconds(1)); + + #endregion + } + + public static void Retry_V8() + { + #region migration-retry-v8 + + // Retry once + new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = 1, + Delay = TimeSpan.Zero, + }) + .Build(); + + // Retry multiple times + new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = 3, + Delay = TimeSpan.Zero, + }) + .Build(); + + // Retry multiple times with callback + new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = 3, + Delay = TimeSpan.Zero, + OnRetry = args => + { + // Add logic to be executed before each retry, such as logging + return default; + } + }) + .Build(); + + // Retry forever + new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = int.MaxValue, + Delay = TimeSpan.Zero, + }) + .Build(); + + // Wait and retry multiple times + new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = 3, + Delay = TimeSpan.FromSeconds(1), + BackoffType = DelayBackoffType.Constant + }) + .Build(); + + // Wait and retry multiple times with callback + new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = 3, + Delay = TimeSpan.FromSeconds(1), + BackoffType = DelayBackoffType.Constant, + OnRetry = args => + { + // Add logic to be executed before each retry, such as logging + return default; + } + }) + .Build(); + + // Wait and retry forever + new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder().Handle(), + MaxRetryAttempts = int.MaxValue, + Delay = TimeSpan.FromSeconds(1), + BackoffType = DelayBackoffType.Constant + }) + .Build(); + + #endregion + } +} diff --git a/src/Snippets/Docs/Migration.Wrap.cs b/src/Snippets/Docs/Migration.Wrap.cs new file mode 100644 index 00000000000..d4e3295b8a5 --- /dev/null +++ b/src/Snippets/Docs/Migration.Wrap.cs @@ -0,0 +1,41 @@ +namespace Snippets.Docs; + +internal static partial class Migration +{ + public static void PolicyWrap_V7() + { + #region migration-policy-wrap-v7 + + IAsyncPolicy retryPolicy = Policy.Handle().WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1)); + + IAsyncPolicy timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(3)); + + // Wrap the policies. Tne policies are executed in the following order: + // 1. Retry + // 2. Timeout + IAsyncPolicy wrappedPolicy = Policy.WrapAsync(timeoutPolicy, retryPolicy); + + #endregion + } + + public static void PolicyWrap_V8() + { + #region migration-policy-wrap-v8 + + // The "PolicyWrap" is integrated directly. Strategies are executed in the same order as they were added: + // 1. Retry + // 2. Timeout + ResiliencePipeline pipeline = new ResiliencePipelineBuilder() + .AddRetry(new() + { + MaxRetryAttempts = 3, + Delay = TimeSpan.FromSeconds(1), + BackoffType = DelayBackoffType.Constant, + ShouldHandle = new PredicateBuilder().Handle() + }) + .AddTimeout(TimeSpan.FromSeconds(3)) + .Build(); + + #endregion + } +} diff --git a/src/Snippets/Docs/RateLimiter.cs b/src/Snippets/Docs/RateLimiter.cs index 1e460abef53..b0e91e65b91 100644 --- a/src/Snippets/Docs/RateLimiter.cs +++ b/src/Snippets/Docs/RateLimiter.cs @@ -1,6 +1,5 @@ using System.Threading.RateLimiting; using Microsoft.Extensions.DependencyInjection; -using Polly; using Polly.RateLimiting; namespace Snippets.Docs; @@ -12,7 +11,7 @@ public static void Usage() #region rate-limiter // Add rate limiter with default options. - // See https://github.com/App-vNext/Polly/blob/main/docs/strategies/rate-limiter.md#defaults for default values. + // See https://www.pollydocs.org/strategies/rate-limiter#defaults for defaults. new ResiliencePipelineBuilder() .AddRateLimiter(new RateLimiterStrategyOptions()); diff --git a/src/Snippets/Docs/Readme.cs b/src/Snippets/Docs/Readme.cs index b0b2d1805b1..7e1b55f87d1 100644 --- a/src/Snippets/Docs/Readme.cs +++ b/src/Snippets/Docs/Readme.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using Polly; using Polly.Registry; using Polly.Retry; diff --git a/src/Snippets/Docs/ResilienceContextUsage.cs b/src/Snippets/Docs/ResilienceContextUsage.cs index f89130a0881..17554412dec 100644 --- a/src/Snippets/Docs/ResilienceContextUsage.cs +++ b/src/Snippets/Docs/ResilienceContextUsage.cs @@ -1,6 +1,4 @@ -using Polly; - -namespace Snippets.Docs; +namespace Snippets.Docs; internal static class ResilienceContextUsage { @@ -25,7 +23,6 @@ public static async Task Usage() ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken); // Attach custom data to the context - context.Properties.Set(MyResilienceKeys.Key1, "my-data"); context.Properties.Set(MyResilienceKeys.Key2, 123); diff --git a/src/Snippets/Docs/ResiliencePipelineRegistry.cs b/src/Snippets/Docs/ResiliencePipelineRegistry.cs index 67fc882d73b..384121648d5 100644 --- a/src/Snippets/Docs/ResiliencePipelineRegistry.cs +++ b/src/Snippets/Docs/ResiliencePipelineRegistry.cs @@ -1,6 +1,4 @@ using System.Net.Http; -using System.Threading; -using Polly; using Polly.Registry; using Polly.Retry; diff --git a/src/Snippets/Docs/ResiliencePipelines.cs b/src/Snippets/Docs/ResiliencePipelines.cs index b281ca5a00b..012b2e2284d 100644 --- a/src/Snippets/Docs/ResiliencePipelines.cs +++ b/src/Snippets/Docs/ResiliencePipelines.cs @@ -1,6 +1,5 @@ using System.Net.Http; using Microsoft.Extensions.DependencyInjection; -using Polly; using Polly.Registry; namespace Snippets.Docs; diff --git a/src/Snippets/Docs/ResilienceStrategies.cs b/src/Snippets/Docs/ResilienceStrategies.cs index 02fd87080d9..4af04756d32 100644 --- a/src/Snippets/Docs/ResilienceStrategies.cs +++ b/src/Snippets/Docs/ResilienceStrategies.cs @@ -1,5 +1,4 @@ using System.Net.Http; -using Polly; using Polly.Retry; using Polly.Timeout; diff --git a/src/Snippets/Docs/Retry.cs b/src/Snippets/Docs/Retry.cs index a67653d18b9..e784be442cb 100644 --- a/src/Snippets/Docs/Retry.cs +++ b/src/Snippets/Docs/Retry.cs @@ -1,6 +1,5 @@ using System.Globalization; using System.Net.Http; -using Polly; using Polly.Retry; using Snippets.Docs.Utils; @@ -13,7 +12,7 @@ public static void Usage() #region retry // Add retry using the default options. - // See https://github.com/App-vNext/Polly/blob/main/docs/strategies/retry.md#defaults for default values. + // See https://www.pollydocs.org/strategies/retry#defaults for defaults. new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions()); // For instant retries with no delay diff --git a/src/Snippets/Docs/Telemetry.cs b/src/Snippets/Docs/Telemetry.cs index afa079efedf..09de96d8865 100644 --- a/src/Snippets/Docs/Telemetry.cs +++ b/src/Snippets/Docs/Telemetry.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Polly; using Polly.Retry; using Polly.Telemetry; diff --git a/src/Snippets/Docs/Timeout.cs b/src/Snippets/Docs/Timeout.cs index 929ecd22faf..5fd38d42127 100644 --- a/src/Snippets/Docs/Timeout.cs +++ b/src/Snippets/Docs/Timeout.cs @@ -1,6 +1,4 @@ -using System; -using System.Net.Http; -using Polly; +using System.Net.Http; using Polly.Timeout; namespace Snippets.Docs; @@ -12,7 +10,7 @@ public static async Task Usage() #region timeout // Add timeout using the default options. - // See https://github.com/App-vNext/Polly/blob/main/docs/strategies/timeout.md#defaults for default values. + // See https://www.pollydocs.org/strategies/timeout#defaults for defaults. new ResiliencePipelineBuilder() .AddTimeout(new TimeoutStrategyOptions()); diff --git a/src/Snippets/RateLimiting/Snippets.cs b/src/Snippets/RateLimiting/Snippets.cs index 0cd2c9b0a66..51720b84ecd 100644 --- a/src/Snippets/RateLimiting/Snippets.cs +++ b/src/Snippets/RateLimiting/Snippets.cs @@ -1,5 +1,4 @@ using System.Threading.RateLimiting; -using Polly; using Polly.RateLimiting; namespace Snippets.RateLimiting; diff --git a/src/Snippets/Snippets.csproj b/src/Snippets/Snippets.csproj index faaa521637c..9e480f35c09 100644 --- a/src/Snippets/Snippets.csproj +++ b/src/Snippets/Snippets.csproj @@ -8,7 +8,7 @@ Library false false - $(NoWarn);SA1123;SA1515;CA2000;CA2007;CA1303;IDE0021;IDE0017;IDE0060;CS1998;CA1064;S3257 + $(NoWarn);SA1123;SA1515;CA2000;CA2007;CA1303;IDE0021;IDE0017;IDE0060;CS1998;CA1064;S3257;IDE0028 Snippets @@ -21,6 +21,9 @@ + + + diff --git a/src/Snippets/Testing/Snippets.cs b/src/Snippets/Testing/Snippets.cs index 11e2a6539fb..39a973b3433 100644 --- a/src/Snippets/Testing/Snippets.cs +++ b/src/Snippets/Testing/Snippets.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging.Abstractions; -using Polly; using Polly.Retry; using Polly.Testing; using Polly.Timeout;