diff --git a/docs/advanced/resilience-context.md b/docs/advanced/resilience-context.md index 0fa35e48273..798a7d53020 100644 --- a/docs/advanced/resilience-context.md +++ b/docs/advanced/resilience-context.md @@ -9,6 +9,11 @@ The resilience context exposes several properties: - `Properties`: An instance of `ResilienceProperties` for attaching custom data to the context. - `ContinueOnCapturedContext`: Specifies whether the asynchronous execution should continue on the captured context. +> [!IMPORTANT] +> When using a custom `ResilienceContext`, ensure that your usage is correct to avoid the context being treated as custom +> _state_ for the execution instead of as the _context_ for the execution. Otherwise, the delegate invoked by the resilience +> pipeline will be a different instance obtained from the shared pool, rather than the value specified for your execution. + ## Usage Below is an example demonstrating how to work with `ResilienceContext`: diff --git a/test/Polly.Core.Tests/Issues/IssuesTests .OnRetryContext_2597.cs b/test/Polly.Core.Tests/Issues/IssuesTests .OnRetryContext_2597.cs new file mode 100644 index 00000000000..83ccfbcdc0a --- /dev/null +++ b/test/Polly.Core.Tests/Issues/IssuesTests .OnRetryContext_2597.cs @@ -0,0 +1,50 @@ +namespace Polly.Core.Tests.Issues; + +public partial class IssuesTests +{ + [Fact] + public async Task OnRetry_Contains_User_Context_2597() + { + // Arrange + var propertyKey = Guid.NewGuid().ToString(); + var propertyValue = Guid.NewGuid().ToString(); + + var key = new ResiliencePropertyKey(propertyKey); + + var pipeline = new ResiliencePipelineBuilder() + .AddRetry(new() + { + MaxRetryAttempts = 2, + OnRetry = (args) => + { + args.Context.Properties.TryGetValue(key, out var actualValue).ShouldBeTrue(); + actualValue.ShouldBe(propertyValue); + return default; + } + }) + .Build(); + + var executed = false; + + // Act + var context = ResilienceContextPool.Shared.Get(); + context.Properties.Set(key, propertyValue); + + var actual = await Should.ThrowAsync( + async () => await pipeline.ExecuteAsync( + (context) => + { + context.Properties.TryGetValue(key, out var actualValue).ShouldBeTrue(); + actualValue.ShouldBe(propertyValue); + + executed = true; + + throw new InvalidOperationException(propertyValue); + }, + context)); + + // Assert + actual.Message.ShouldBe(propertyValue); + executed.ShouldBeTrue(); + } +}