From 950a4fd70e3620b537eb620fbf56b314a4a152e8 Mon Sep 17 00:00:00 2001 From: carvex21 Date: Wed, 20 Aug 2025 20:25:59 +0200 Subject: [PATCH] fix documentation for ExecuteOutcomeAsync (#2680) --- docs/migration-v8.md | 38 ++++++++++++++++++--- docs/strategies/circuit-breaker.md | 22 +++++++++--- docs/strategies/fallback.md | 18 +++++++--- src/Polly.Core/ResiliencePipeline.AsyncT.cs | 7 ++-- src/Polly.Core/ResiliencePipelineT.Async.cs | 7 ++-- src/Snippets/Docs/CircuitBreaker.cs | 22 +++++++++--- src/Snippets/Docs/Fallback.cs | 18 +++++++--- src/Snippets/Docs/Migration.Execute.cs | 38 ++++++++++++++++++--- 8 files changed, 138 insertions(+), 32 deletions(-) diff --git a/docs/migration-v8.md b/docs/migration-v8.md index 3ae3aadbe04..273042c5c6f 100644 --- a/docs/migration-v8.md +++ b/docs/migration-v8.md @@ -936,8 +936,23 @@ ResiliencePipeline pipeline = new ResiliencePipelineBuilder() // Asynchronous execution var context = ResilienceContextPool.Shared.Get(); -Outcome pipelineResult = await pipeline.ExecuteOutcomeAsync( - static async (ctx, state) => Outcome.FromResult(await MethodAsync(ctx.CancellationToken)), context, "state"); + +Outcome pipelineResult = + await pipeline.ExecuteOutcomeAsync( + static async (ctx, state) => + { + try + { + return Outcome.FromResult(await MethodAsync(ctx.CancellationToken)); + } + catch (Exception e) + { + return Outcome.FromException(e); + } + }, + context, + "state"); + ResilienceContextPool.Shared.Return(context); // Assess policy result @@ -950,7 +965,6 @@ if (pipelineResult.Exception is null) else { Exception exception = pipelineResult.Exception; - // Process failure // If needed you can rethrow the exception @@ -972,8 +986,22 @@ ResiliencePipeline pipelineWithContext = new ResiliencePipelineBuilder .Build(); context = ResilienceContextPool.Shared.Get(); -pipelineResult = await pipelineWithContext.ExecuteOutcomeAsync( - static async (ctx, state) => Outcome.FromResult(await MethodAsync(ctx.CancellationToken)), context, "state"); + +pipelineResult = + await pipelineWithContext.ExecuteOutcomeAsync( + static async (ctx, state) => + { + try + { + return Outcome.FromResult(await MethodAsync(ctx.CancellationToken)); + } + catch (Exception e) + { + return Outcome.FromException(e); + } + }, + context, + "state"); context.Properties.TryGetValue(contextKey, out var ctxValue); ResilienceContextPool.Shared.Return(context); diff --git a/docs/strategies/circuit-breaker.md b/docs/strategies/circuit-breaker.md index 22ed6d0c38b..7a18f4b2fb9 100644 --- a/docs/strategies/circuit-breaker.md +++ b/docs/strategies/circuit-breaker.md @@ -675,6 +675,7 @@ Use `ExecuteOutcomeAsync` to avoid throwing exception: ```cs var context = ResilienceContextPool.Shared.Get(); + var circuitBreaker = new ResiliencePipelineBuilder() .AddCircuitBreaker(new() { @@ -683,11 +684,22 @@ var circuitBreaker = new ResiliencePipelineBuilder() }) .Build(); -Outcome outcome = await circuitBreaker.ExecuteOutcomeAsync(static async (ctx, state) => -{ - var response = await IssueRequest(); - return Outcome.FromResult(response); -}, context, "state"); +Outcome outcome = + await circuitBreaker.ExecuteOutcomeAsync( + static async (ctx, state) => + { + try + { + var response = await IssueRequest(ctx.CancellationToken); + return Outcome.FromResult(response); + } + catch (Exception e) + { + return Outcome.FromException(e); + } + }, + context, + "state"); ResilienceContextPool.Shared.Return(context); diff --git a/docs/strategies/fallback.md b/docs/strategies/fallback.md index c5c4012ac5c..c87806cabb3 100644 --- a/docs/strategies/fallback.md +++ b/docs/strategies/fallback.md @@ -239,12 +239,22 @@ This method lets you execute the strategy or pipeline smoothly, without unexpect public static async ValueTask Action() { var context = ResilienceContextPool.Shared.Get(); + var outcome = await WhateverPipeline.ExecuteOutcomeAsync( - async (ctx, state) => + static async (ctx, state) => { - var result = await ActionCore(); - return Outcome.FromResult(result); - }, context, "state"); + try + { + var result = await ActionCore(ctx.CancellationToken); + return Outcome.FromResult(result); + } + catch (Exception e) + { + return Outcome.FromException(e); + } + }, + context, + "state"); if (outcome.Exception is HttpRequestException requestException) { diff --git a/src/Polly.Core/ResiliencePipeline.AsyncT.cs b/src/Polly.Core/ResiliencePipeline.AsyncT.cs index 72e9d85ce35..dc2667a53a5 100644 --- a/src/Polly.Core/ResiliencePipeline.AsyncT.cs +++ b/src/Polly.Core/ResiliencePipeline.AsyncT.cs @@ -16,8 +16,11 @@ public partial class ResiliencePipeline /// The instance of that represents the asynchronous execution. /// Thrown when or is . /// - /// This method is for advanced and high performance scenarios. The caller must make sure that the - /// does not throw any exceptions. Instead, it converts them to . + /// Important: This API targets advanced, low-allocation scenarios. The user callback + /// must not throw an exception. Wrap your code and return : + /// use on success, or on failure. + /// Do not rely on strategies to catch your exceptions; any such behavior is an implementation detail and is not + /// guaranteed across strategies or future versions. /// public ValueTask> ExecuteOutcomeAsync( Func>> callback, diff --git a/src/Polly.Core/ResiliencePipelineT.Async.cs b/src/Polly.Core/ResiliencePipelineT.Async.cs index fa8c2cb6cd3..6934e07880a 100644 --- a/src/Polly.Core/ResiliencePipelineT.Async.cs +++ b/src/Polly.Core/ResiliencePipelineT.Async.cs @@ -77,8 +77,11 @@ public ValueTask ExecuteAsync( /// The instance of that represents the asynchronous execution. /// Thrown when or is . /// - /// This method is for advanced and high performance scenarios. The caller must make sure that the - /// does not throw any exceptions. Instead, it converts them to . + /// Important: This method targets advanced, low-allocation scenarios. The user callback + /// must not throw an exception. Wrap your code and return : + /// use on success, or on failure. + /// Do not rely on strategies to catch your exceptions; any such behavior is an implementation detail and + /// is not guaranteed across strategies or future versions. /// public ValueTask> ExecuteOutcomeAsync( Func>> callback, diff --git a/src/Snippets/Docs/CircuitBreaker.cs b/src/Snippets/Docs/CircuitBreaker.cs index 3e0e136a7d6..087a6bc4e36 100644 --- a/src/Snippets/Docs/CircuitBreaker.cs +++ b/src/Snippets/Docs/CircuitBreaker.cs @@ -270,6 +270,7 @@ public static async ValueTask Pattern_ReduceThrownExceptions() #region circuit-breaker-pattern-reduce-thrown-exceptions var context = ResilienceContextPool.Shared.Get(); + var circuitBreaker = new ResiliencePipelineBuilder() .AddCircuitBreaker(new() { @@ -278,11 +279,22 @@ public static async ValueTask Pattern_ReduceThrownExceptions() }) .Build(); - Outcome outcome = await circuitBreaker.ExecuteOutcomeAsync(static async (ctx, state) => - { - var response = await IssueRequest(); - return Outcome.FromResult(response); - }, context, "state"); + Outcome outcome = + await circuitBreaker.ExecuteOutcomeAsync( + static async (ctx, state) => + { + try + { + var response = await IssueRequest(); + return Outcome.FromResult(response); + } + catch (Exception e) + { + return Outcome.FromException(e); + } + }, + context, + "state"); ResilienceContextPool.Shared.Return(context); diff --git a/src/Snippets/Docs/Fallback.cs b/src/Snippets/Docs/Fallback.cs index 495f21f8c9a..5b7b9589d5c 100644 --- a/src/Snippets/Docs/Fallback.cs +++ b/src/Snippets/Docs/Fallback.cs @@ -118,12 +118,22 @@ public static async Task Pattern_ReplaceException() public static async ValueTask Action() { var context = ResilienceContextPool.Shared.Get(); + var outcome = await WhateverPipeline.ExecuteOutcomeAsync( - async (ctx, state) => + static async (ctx, state) => { - var result = await ActionCore(); - return Outcome.FromResult(result); - }, context, "state"); + try + { + var result = await ActionCore(); + return Outcome.FromResult(result); + } + catch (Exception e) + { + return Outcome.FromException(e); + } + }, + context, + "state"); if (outcome.Exception is HttpRequestException requestException) { diff --git a/src/Snippets/Docs/Migration.Execute.cs b/src/Snippets/Docs/Migration.Execute.cs index 11760d4aff4..8810f18e4f5 100644 --- a/src/Snippets/Docs/Migration.Execute.cs +++ b/src/Snippets/Docs/Migration.Execute.cs @@ -59,8 +59,23 @@ 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(ctx.CancellationToken)), context, "state"); + + Outcome pipelineResult = + await pipeline.ExecuteOutcomeAsync( + static async (ctx, state) => + { + try + { + return Outcome.FromResult(await MethodAsync(ctx.CancellationToken)); + } + catch (Exception e) + { + return Outcome.FromException(e); + } + }, + context, + "state"); + ResilienceContextPool.Shared.Return(context); // Assess policy result @@ -73,7 +88,6 @@ public static async Task SafeExecute_V8() else { Exception exception = pipelineResult.Exception; - // Process failure // If needed you can rethrow the exception @@ -95,8 +109,22 @@ public static async Task SafeExecute_V8() .Build(); context = ResilienceContextPool.Shared.Get(); - pipelineResult = await pipelineWithContext.ExecuteOutcomeAsync( - static async (ctx, state) => Outcome.FromResult(await MethodAsync(ctx.CancellationToken)), context, "state"); + + pipelineResult = + await pipelineWithContext.ExecuteOutcomeAsync( + static async (ctx, state) => + { + try + { + return Outcome.FromResult(await MethodAsync(ctx.CancellationToken)); + } + catch (Exception e) + { + return Outcome.FromException(e); + } + }, + context, + "state"); context.Properties.TryGetValue(contextKey, out var ctxValue); ResilienceContextPool.Shared.Return(context);