diff --git a/docs/migration-v8.md b/docs/migration-v8.md index f9391ed629..43ed6f2513 100644 --- a/docs/migration-v8.md +++ b/docs/migration-v8.md @@ -596,6 +596,112 @@ 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 an exception in case of failure rather than wrap the outcome in a result object. + +In v8, the `ExecuteOutcomeAsync` method 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, CancellationToken.None); + +// Assess policy result +if (policyResult.Outcome == OutcomeType.Successful) +{ + int result = policyResult.Result; + + // Process result +} +else +{ + Exception 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, token) => MethodAsync(token), new Context(), CancellationToken.None); +string? ctxValue = asyncPolicyResult.Context.GetValueOrDefault("context_key") as string; +``` + + +### `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(ctx.CancellationToken)), context, "state"); +ResilienceContextPool.Shared.Return(context); + +// Assess policy result +if (pipelineResult.Exception is null) +{ + int result = pipelineResult.Result; + + // Process result +} +else +{ + Exception 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(ctx.CancellationToken)), context, "state"); + +context.Properties.TryGetValue(contextKey, out var ctxValue); +ResilienceContextPool.Shared.Return(context); +``` + + ## Migrating no-op policies - 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 0000000000..4ed92c9d12 --- /dev/null +++ b/src/Snippets/Docs/Migration.Execute.cs @@ -0,0 +1,104 @@ +using Polly.Timeout; + +namespace Snippets.Docs; + +internal static partial class Migration +{ + private static int Method() => 3; + private static Task MethodAsync(CancellationToken token) => 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, CancellationToken.None); + + // Assess policy result + if (policyResult.Outcome == OutcomeType.Successful) + { + int result = policyResult.Result; + + // Process result + } + else + { + Exception 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, token) => MethodAsync(token), new Context(), CancellationToken.None); + string? ctxValue = asyncPolicyResult.Context.GetValueOrDefault("context_key") as string; + #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(ctx.CancellationToken)), context, "state"); + ResilienceContextPool.Shared.Return(context); + + // Assess policy result + if (pipelineResult.Exception is null) + { + int result = pipelineResult.Result; + + // Process result + } + else + { + Exception 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(ctx.CancellationToken)), context, "state"); + + context.Properties.TryGetValue(contextKey, out var ctxValue); + ResilienceContextPool.Shared.Return(context); + #endregion + } +}