From 7da98f9bfa001b27fb63601bbb28a991009de5f5 Mon Sep 17 00:00:00 2001 From: "peter.csala" Date: Wed, 27 Sep 2023 09:30:40 +0200 Subject: [PATCH 1/3] Add safe execution section to the migration guide --- docs/migration-v8.md | 102 +++++++++++++++++++++++++ src/Snippets/Docs/Migration.Execute.cs | 100 ++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 src/Snippets/Docs/Migration.Execute.cs diff --git a/docs/migration-v8.md b/docs/migration-v8.md index 4484cfd4147..3bb383bf14d 100644 --- a/docs/migration-v8.md +++ b/docs/migration-v8.md @@ -596,6 +596,108 @@ ResilienceContextPool.Shared.Return(context); For more details, refer to the [Resilience context](advanced/resilience-context.md) documentation. +## Migrating safe execution + +In v7, the `ExecuteAndCapture(Async)` methods are considered the safe counterpart of the `Execute(Async)`. + +The former does not throw exception in case of failure rather than wraps the outcome into a result object. + +In v8, the `ExecuteOutcomeAsync` should be used to execute the to-be-decorated method in a safe way. + +### `ExecuteAndCapture(Async)` in V7 + + +```cs +// Synchronous execution +ISyncPolicy syncPolicy = Policy.Timeout(TimeSpan.FromSeconds(1)); +PolicyResult policyResult = syncPolicy.ExecuteAndCapture(Method); + +// Asynchronous execution +IAsyncPolicy asyncPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1)); +PolicyResult asyncPolicyResult = await asyncPolicy.ExecuteAndCaptureAsync(MethodAsync); + +// Assess policy result +if (policyResult.Outcome == OutcomeType.Successful) +{ + var result = policyResult.Result; + // process result +} +else +{ + var exception = policyResult.FinalException; + FaultType failtType = policyResult.FaultType!.Value; + ExceptionType exceptionType = policyResult.ExceptionType!.Value; + // process failure +} + +// Access context +IAsyncPolicy asyncPolicyWithContext = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), + onTimeoutAsync: (ctx, ts, task) => + { + ctx["context_key"] = "context_value"; + return Task.CompletedTask; + }); + +asyncPolicyResult = await asyncPolicyWithContext.ExecuteAndCaptureAsync((ctx) => MethodAsync(), new Context()); +var ctxValue = asyncPolicyResult.Context.GetValueOrDefault("context_key"); +``` + + +### `ExecuteOutcomeAsync` in V8 + + +```cs +ResiliencePipeline pipeline = new ResiliencePipelineBuilder() + .AddTimeout(TimeSpan.FromSeconds(1)) + .Build(); + +// Synchronous execution +// Polly v8 does not provide an API to synchronously execute and capture the outcome of a pipeline + +// Asynchronous execution +var context = ResilienceContextPool.Shared.Get(); +Outcome pipelineResult = await pipeline.ExecuteOutcomeAsync( + static async (ctx, state) => Outcome.FromResult(await MethodAsync()), context, "state"); +ResilienceContextPool.Shared.Return(context); + +// Assess policy result +if (pipelineResult.Exception is null) +{ + var result = pipelineResult.Result; + // process result +} +else +{ + var exception = pipelineResult.Exception; + // process failure + + // if needed you can rethrow the exception + pipelineResult.ThrowIfException(); +} + +// Access context +ResiliencePropertyKey contextKey = new("context_key"); +ResiliencePipeline pipelineWithContext = new ResiliencePipelineBuilder() + .AddTimeout(new TimeoutStrategyOptions + { + Timeout = TimeSpan.FromSeconds(1), + OnTimeout = args => + { + args.Context.Properties.Set(contextKey, "context_value"); + return default; + } + }) + .Build(); + +context = ResilienceContextPool.Shared.Get(); +pipelineResult = await pipelineWithContext.ExecuteOutcomeAsync( + static async (ctx, state) => Outcome.FromResult(await MethodAsync()), context, "state"); + +context.Properties.TryGetValue(contextKey, out var ctxValue); +ResilienceContextPool.Shared.Return(context); +``` + + ## Migrating no-op policy - For `Policy.NoOp` or `Policy.NoOpAsync`, switch to `ResiliencePipeline.Empty`. diff --git a/src/Snippets/Docs/Migration.Execute.cs b/src/Snippets/Docs/Migration.Execute.cs new file mode 100644 index 00000000000..9e5dd1996ae --- /dev/null +++ b/src/Snippets/Docs/Migration.Execute.cs @@ -0,0 +1,100 @@ +using Polly.Timeout; + +namespace Snippets.Docs; + +internal static partial class Migration +{ + private static int Method() => 3; + private static Task MethodAsync() => Task.FromResult(3); + public static async Task SafeExecute_V7() + { + #region migration-execute-v7 + // Synchronous execution + ISyncPolicy syncPolicy = Policy.Timeout(TimeSpan.FromSeconds(1)); + PolicyResult policyResult = syncPolicy.ExecuteAndCapture(Method); + + // Asynchronous execution + IAsyncPolicy asyncPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1)); + PolicyResult asyncPolicyResult = await asyncPolicy.ExecuteAndCaptureAsync(MethodAsync); + + // Assess policy result + if (policyResult.Outcome == OutcomeType.Successful) + { + var result = policyResult.Result; + // process result + } + else + { + var exception = policyResult.FinalException; + FaultType failtType = policyResult.FaultType!.Value; + ExceptionType exceptionType = policyResult.ExceptionType!.Value; + // process failure + } + + // Access context + IAsyncPolicy asyncPolicyWithContext = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), + onTimeoutAsync: (ctx, ts, task) => + { + ctx["context_key"] = "context_value"; + return Task.CompletedTask; + }); + + asyncPolicyResult = await asyncPolicyWithContext.ExecuteAndCaptureAsync((ctx) => MethodAsync(), new Context()); + var ctxValue = asyncPolicyResult.Context.GetValueOrDefault("context_key"); + #endregion + } + + public static async Task SafeExecute_V8() + { + #region migration-execute-v8 + ResiliencePipeline pipeline = new ResiliencePipelineBuilder() + .AddTimeout(TimeSpan.FromSeconds(1)) + .Build(); + + // Synchronous execution + // Polly v8 does not provide an API to synchronously execute and capture the outcome of a pipeline + + // Asynchronous execution + var context = ResilienceContextPool.Shared.Get(); + Outcome pipelineResult = await pipeline.ExecuteOutcomeAsync( + static async (ctx, state) => Outcome.FromResult(await MethodAsync()), context, "state"); + ResilienceContextPool.Shared.Return(context); + + // Assess policy result + if (pipelineResult.Exception is null) + { + var result = pipelineResult.Result; + // process result + } + else + { + var exception = pipelineResult.Exception; + // process failure + + // if needed you can rethrow the exception + pipelineResult.ThrowIfException(); + } + + // Access context + ResiliencePropertyKey contextKey = new("context_key"); + ResiliencePipeline pipelineWithContext = new ResiliencePipelineBuilder() + .AddTimeout(new TimeoutStrategyOptions + { + Timeout = TimeSpan.FromSeconds(1), + OnTimeout = args => + { + args.Context.Properties.Set(contextKey, "context_value"); + return default; + } + }) + .Build(); + + context = ResilienceContextPool.Shared.Get(); + pipelineResult = await pipelineWithContext.ExecuteOutcomeAsync( + static async (ctx, state) => Outcome.FromResult(await MethodAsync()), context, "state"); + + context.Properties.TryGetValue(contextKey, out var ctxValue); + ResilienceContextPool.Shared.Return(context); + #endregion + } +} From a9dfe5b525c00f1f99da8020f565eb2e080beebf Mon Sep 17 00:00:00 2001 From: "peter.csala" Date: Wed, 27 Sep 2023 09:59:23 +0200 Subject: [PATCH 2/3] Improve safe execution examples based on feedback --- docs/migration-v8.md | 28 ++++++++++++------------ src/Snippets/Docs/Migration.Execute.cs | 30 +++++++++++++------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/migration-v8.md b/docs/migration-v8.md index 3bb383bf14d..678954450ba 100644 --- a/docs/migration-v8.md +++ b/docs/migration-v8.md @@ -614,20 +614,20 @@ PolicyResult policyResult = syncPolicy.ExecuteAndCapture(Method); // Asynchronous execution IAsyncPolicy asyncPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1)); -PolicyResult asyncPolicyResult = await asyncPolicy.ExecuteAndCaptureAsync(MethodAsync); +PolicyResult asyncPolicyResult = await asyncPolicy.ExecuteAndCaptureAsync(MethodAsync, CancellationToken.None); // Assess policy result if (policyResult.Outcome == OutcomeType.Successful) { - var result = policyResult.Result; - // process result + int result = policyResult.Result; + // Process result } else { - var exception = policyResult.FinalException; + Exception exception = policyResult.FinalException; FaultType failtType = policyResult.FaultType!.Value; ExceptionType exceptionType = policyResult.ExceptionType!.Value; - // process failure + // Process failure } // Access context @@ -638,8 +638,8 @@ IAsyncPolicy asyncPolicyWithContext = Policy.TimeoutAsync(TimeSpan.Fro return Task.CompletedTask; }); -asyncPolicyResult = await asyncPolicyWithContext.ExecuteAndCaptureAsync((ctx) => MethodAsync(), new Context()); -var ctxValue = asyncPolicyResult.Context.GetValueOrDefault("context_key"); +asyncPolicyResult = await asyncPolicyWithContext.ExecuteAndCaptureAsync((ctx, token) => MethodAsync(token), new Context(), CancellationToken.None); +string? ctxValue = asyncPolicyResult.Context.GetValueOrDefault("context_key") as string; ``` @@ -657,21 +657,21 @@ ResiliencePipeline pipeline = new ResiliencePipelineBuilder() // Asynchronous execution var context = ResilienceContextPool.Shared.Get(); Outcome pipelineResult = await pipeline.ExecuteOutcomeAsync( - static async (ctx, state) => Outcome.FromResult(await MethodAsync()), context, "state"); + static async (ctx, state) => Outcome.FromResult(await MethodAsync(ctx.CancellationToken)), context, "state"); ResilienceContextPool.Shared.Return(context); // Assess policy result if (pipelineResult.Exception is null) { - var result = pipelineResult.Result; - // process result + int result = pipelineResult.Result; + // Process result } else { - var exception = pipelineResult.Exception; - // process failure + Exception exception = pipelineResult.Exception; + // Process failure - // if needed you can rethrow the exception + // If needed then you can rethrow the exception pipelineResult.ThrowIfException(); } @@ -691,7 +691,7 @@ ResiliencePipeline pipelineWithContext = new ResiliencePipelineBuilder context = ResilienceContextPool.Shared.Get(); pipelineResult = await pipelineWithContext.ExecuteOutcomeAsync( - static async (ctx, state) => Outcome.FromResult(await MethodAsync()), context, "state"); + static async (ctx, state) => Outcome.FromResult(await MethodAsync(ctx.CancellationToken)), context, "state"); context.Properties.TryGetValue(contextKey, out var ctxValue); ResilienceContextPool.Shared.Return(context); diff --git a/src/Snippets/Docs/Migration.Execute.cs b/src/Snippets/Docs/Migration.Execute.cs index 9e5dd1996ae..9840440f83c 100644 --- a/src/Snippets/Docs/Migration.Execute.cs +++ b/src/Snippets/Docs/Migration.Execute.cs @@ -5,7 +5,7 @@ namespace Snippets.Docs; internal static partial class Migration { private static int Method() => 3; - private static Task MethodAsync() => Task.FromResult(3); + private static Task MethodAsync(CancellationToken token) => Task.FromResult(3); public static async Task SafeExecute_V7() { #region migration-execute-v7 @@ -15,20 +15,20 @@ public static async Task SafeExecute_V7() // Asynchronous execution IAsyncPolicy asyncPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(1)); - PolicyResult asyncPolicyResult = await asyncPolicy.ExecuteAndCaptureAsync(MethodAsync); + PolicyResult asyncPolicyResult = await asyncPolicy.ExecuteAndCaptureAsync(MethodAsync, CancellationToken.None); // Assess policy result if (policyResult.Outcome == OutcomeType.Successful) { - var result = policyResult.Result; - // process result + int result = policyResult.Result; + // Process result } else { - var exception = policyResult.FinalException; + Exception exception = policyResult.FinalException; FaultType failtType = policyResult.FaultType!.Value; ExceptionType exceptionType = policyResult.ExceptionType!.Value; - // process failure + // Process failure } // Access context @@ -39,8 +39,8 @@ public static async Task SafeExecute_V7() return Task.CompletedTask; }); - asyncPolicyResult = await asyncPolicyWithContext.ExecuteAndCaptureAsync((ctx) => MethodAsync(), new Context()); - var ctxValue = asyncPolicyResult.Context.GetValueOrDefault("context_key"); + asyncPolicyResult = await asyncPolicyWithContext.ExecuteAndCaptureAsync((ctx, token) => MethodAsync(token), new Context(), CancellationToken.None); + string? ctxValue = asyncPolicyResult.Context.GetValueOrDefault("context_key") as string; #endregion } @@ -57,21 +57,21 @@ public static async Task SafeExecute_V8() // Asynchronous execution var context = ResilienceContextPool.Shared.Get(); Outcome pipelineResult = await pipeline.ExecuteOutcomeAsync( - static async (ctx, state) => Outcome.FromResult(await MethodAsync()), context, "state"); + static async (ctx, state) => Outcome.FromResult(await MethodAsync(ctx.CancellationToken)), context, "state"); ResilienceContextPool.Shared.Return(context); // Assess policy result if (pipelineResult.Exception is null) { - var result = pipelineResult.Result; - // process result + int result = pipelineResult.Result; + // Process result } else { - var exception = pipelineResult.Exception; - // process failure + Exception exception = pipelineResult.Exception; + // Process failure - // if needed you can rethrow the exception + // If needed then you can rethrow the exception pipelineResult.ThrowIfException(); } @@ -91,7 +91,7 @@ public static async Task SafeExecute_V8() context = ResilienceContextPool.Shared.Get(); pipelineResult = await pipelineWithContext.ExecuteOutcomeAsync( - static async (ctx, state) => Outcome.FromResult(await MethodAsync()), context, "state"); + static async (ctx, state) => Outcome.FromResult(await MethodAsync(ctx.CancellationToken)), context, "state"); context.Properties.TryGetValue(contextKey, out var ctxValue); ResilienceContextPool.Shared.Return(context); From b90a1122cf93f4391b74f2d8f4ba766cb7f16a83 Mon Sep 17 00:00:00 2001 From: "peter.csala" Date: Wed, 27 Sep 2023 10:09:56 +0200 Subject: [PATCH 3/3] Fix typos in safe execution description & snippets --- docs/migration-v8.md | 14 +++++++++----- src/Snippets/Docs/Migration.Execute.cs | 6 +++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/migration-v8.md b/docs/migration-v8.md index 678954450ba..5505c41f0f2 100644 --- a/docs/migration-v8.md +++ b/docs/migration-v8.md @@ -598,13 +598,13 @@ For more details, refer to the [Resilience context](advanced/resilience-context. ## Migrating safe execution -In v7, the `ExecuteAndCapture(Async)` methods are considered the safe counterpart of the `Execute(Async)`. +In v7, the `ExecuteAndCapture{Async}` methods are considered the safe counterpart of the `Execute{Async}`. -The former does not throw exception in case of failure rather than wraps the outcome into a result object. +The former does not throw an exception in case of failure rather than wrap the outcome in a result object. -In v8, the `ExecuteOutcomeAsync` should be used to execute the to-be-decorated method in a safe way. +In v8, the `ExecuteOutcomeAsync` method should be used to execute the to-be-decorated method in a safe way. -### `ExecuteAndCapture(Async)` in V7 +### `ExecuteAndCapture{Async}` in V7 ```cs @@ -620,6 +620,7 @@ PolicyResult asyncPolicyResult = await asyncPolicy.ExecuteAndCaptureAsync(M if (policyResult.Outcome == OutcomeType.Successful) { int result = policyResult.Result; + // Process result } else @@ -627,6 +628,7 @@ else Exception exception = policyResult.FinalException; FaultType failtType = policyResult.FaultType!.Value; ExceptionType exceptionType = policyResult.ExceptionType!.Value; + // Process failure } @@ -664,14 +666,16 @@ ResilienceContextPool.Shared.Return(context); if (pipelineResult.Exception is null) { int result = pipelineResult.Result; + // Process result } else { Exception exception = pipelineResult.Exception; + // Process failure - // If needed then you can rethrow the exception + // If needed you can rethrow the exception pipelineResult.ThrowIfException(); } diff --git a/src/Snippets/Docs/Migration.Execute.cs b/src/Snippets/Docs/Migration.Execute.cs index 9840440f83c..4ed92c9d122 100644 --- a/src/Snippets/Docs/Migration.Execute.cs +++ b/src/Snippets/Docs/Migration.Execute.cs @@ -21,6 +21,7 @@ public static async Task SafeExecute_V7() if (policyResult.Outcome == OutcomeType.Successful) { int result = policyResult.Result; + // Process result } else @@ -28,6 +29,7 @@ public static async Task SafeExecute_V7() Exception exception = policyResult.FinalException; FaultType failtType = policyResult.FaultType!.Value; ExceptionType exceptionType = policyResult.ExceptionType!.Value; + // Process failure } @@ -64,14 +66,16 @@ public static async Task SafeExecute_V8() if (pipelineResult.Exception is null) { int result = pipelineResult.Result; + // Process result } else { Exception exception = pipelineResult.Exception; + // Process failure - // If needed then you can rethrow the exception + // If needed you can rethrow the exception pipelineResult.ThrowIfException(); }