From 77c54eb2478d67d34c87f5d94d098992cd8990ae Mon Sep 17 00:00:00 2001 From: peter-csala Date: Fri, 8 Dec 2023 11:36:26 +0100 Subject: [PATCH 1/3] Document the difference between state and context --- docs/pipelines/index.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/pipelines/index.md b/docs/pipelines/index.md index a91a420b1f6..15132db500d 100644 --- a/docs/pipelines/index.md +++ b/docs/pipelines/index.md @@ -135,6 +135,31 @@ else Use `ExecuteOutcomeAsync(...)` in high-performance scenarios where you wish to avoid re-throwing exceptions. Keep in mind that Polly's resilience strategies also make use of the `Outcome` struct to prevent unnecessary exception throwing. +### Context vs State + +In the previous example the `ExecuteOutcomeAsync` was called with `"my-state"` state object. You might wonder what's the point of the `state`, or can't we just use the `context`? + +The `state` object was introduced to be able to pass a parameter to the user callback without the usage of closure. + +- It allows you to access any object of the `ExecuteOutcomeAsync`'s caller method without extra memory allocation +- It also enables you to use static anonymous functions + +So, it is a performance optimization tool. Of course you can pass more complex object than just a simple string like `(instance: this, request)`. + +While the `state` object is accessible only inside the user callback, you can use the `context` in many places. +For example in case of Retry the `context` is accessible: + +- Inside the `ShouldHandle` delegate +- Inside the `OnRetry` delegate +- Inside the `DelayGenerator` delegate +- and through the `Outcome` property + +As a rule of thumb: + +- Use the `state` object to pass a parameter to your decorated method +- Use the `context` object to exchange information between delegates of the `XYZOptions` + - or between invocation attempts (in case of retry or hedging strategies) + ## Diagrams ### Sequence diagram for a pipeline with retry and timeout From 2330d352a807e9a4b2493af55a26e6a064382e5c Mon Sep 17 00:00:00 2001 From: peter-csala Date: Fri, 8 Dec 2023 11:36:52 +0100 Subject: [PATCH 2/3] Improve legibility of the ExecuteCoreSync --- src/Polly.Core/Utils/Pipeline/PipelineComponent.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Polly.Core/Utils/Pipeline/PipelineComponent.cs b/src/Polly.Core/Utils/Pipeline/PipelineComponent.cs index 6eb898a424a..8dd6848e404 100644 --- a/src/Polly.Core/Utils/Pipeline/PipelineComponent.cs +++ b/src/Polly.Core/Utils/Pipeline/PipelineComponent.cs @@ -21,17 +21,11 @@ internal Outcome ExecuteCoreSync( Func> callback, ResilienceContext context, TState state) - { - return ExecuteCore( - static (context, state) => - { - var result = state.callback(context, state.state); - - return new ValueTask>(result); - }, + => ExecuteCore( + static (context, state) => new ValueTask>(state.callbackMethod(context, state.stateObject)), context, - (callback, state)).GetResult(); - } + (callbackMethod: callback, stateObject: state)) + .GetResult(); public abstract ValueTask DisposeAsync(); From 1d37cb1940eabdb7b42e2737055e8532d7b1aed5 Mon Sep 17 00:00:00 2001 From: peter-csala <57183693+peter-csala@users.noreply.github.com> Date: Fri, 8 Dec 2023 13:46:55 +0100 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Martin Costello --- docs/pipelines/index.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/pipelines/index.md b/docs/pipelines/index.md index 15132db500d..63d4f71d13b 100644 --- a/docs/pipelines/index.md +++ b/docs/pipelines/index.md @@ -139,26 +139,25 @@ Use `ExecuteOutcomeAsync(...)` in high-performance scenarios where you wish to a In the previous example the `ExecuteOutcomeAsync` was called with `"my-state"` state object. You might wonder what's the point of the `state`, or can't we just use the `context`? -The `state` object was introduced to be able to pass a parameter to the user callback without the usage of closure. +The `state` object was introduced to be able to pass a parameter to the user callback without using a [closure](https://stackoverflow.com/a/428624/1064169). -- It allows you to access any object of the `ExecuteOutcomeAsync`'s caller method without extra memory allocation -- It also enables you to use static anonymous functions +- It allows you to access any object of the `ExecuteOutcomeAsync`'s caller method without any extra memory allocation +- It also enables you to use static anonymous methods So, it is a performance optimization tool. Of course you can pass more complex object than just a simple string like `(instance: this, request)`. While the `state` object is accessible only inside the user callback, you can use the `context` in many places. For example in case of Retry the `context` is accessible: -- Inside the `ShouldHandle` delegate -- Inside the `OnRetry` delegate -- Inside the `DelayGenerator` delegate -- and through the `Outcome` property +- inside the `ShouldHandle` delegate; +- inside the `OnRetry` delegate; +- inside the `DelayGenerator` delegate; +- through the `Outcome` property. As a rule of thumb: -- Use the `state` object to pass a parameter to your decorated method -- Use the `context` object to exchange information between delegates of the `XYZOptions` - - or between invocation attempts (in case of retry or hedging strategies) +- Use the `state` object to pass a parameter to your decorated method; +- Use the `context` object to exchange information between delegates of an instance of `XYZOptions` or between invocation attempts (in the case of retry or hedging strategies). ## Diagrams