GithubHelp home page GithubHelp logo

Comments (9)

martintmk avatar martintmk commented on June 29, 2024 2

Unfortunately, this won't work 100%. For example this piece of code fails:

var outcome = ResiliencePipeline.Empty.ExecuteOutcomeAsync<string, string>(
    (_, _) => throw new InvalidOperationException(),
    ResilienceContextPool.Shared.Get(),
    "dummy");

To fix this, try catch needs to be added here :

return Component.ExecuteCore(callback, context, state);

Basically, we need to define a static lambda and call callback inside it with try/catch block. The reason why I haven't done this is tht async machinery adds constant overhead (around 40ns) on my machine and since this was intended to be high-performance API I thought it's not necessary there.

@peter-csala
Your example works by luck because the retry strategy does exception handling inside. if you added other strategy (custom for example) the test would fail.

from polly.

troygould-stack avatar troygould-stack commented on June 29, 2024 1

As the author of this question, I like that you are putting yourselves in the user's shoes. Thanks for thinking this through. IMO, I think it should work the same for all strategies if possible. Adding some user documentation around opt-in and opt-out is a great idea to add clarity to the situation as well.

from polly.

peter-csala avatar peter-csala commented on June 29, 2024

I've put together two simple unit tests.

[Fact]
public async Task ThrowNotHandledException()
{
    //Arrange
    const int expectedRetryCount = 0;
    int actualRetryCount = 0;
    var context = ResilienceContextPool.Shared.Get();
    var retry = new ResiliencePipelineBuilder<int>()
    .AddRetry(new () {
        ShouldHandle = new PredicateBuilder<int>().Handle<MyException>(),
        MaxRetryAttempts = 3,
        OnRetry = _ =>
        {
            actualRetryCount++;
            return default;
        }
    }).Build();

    //Act
    Outcome<int> actual = await retry.ExecuteOutcomeAsync<int, string>((ctx, stt) => throw new Exception("blah"), context, "a");
    ResilienceContextPool.Shared.Return(context);

    //Assert
    Assert.Equal(expectedRetryCount, actualRetryCount);
    Assert.IsType<Exception>(actual.Exception);
}
[Fact]
public async Task ThrowHandledException()
{
    //Arrange
    const int expectedRetryCount = 3;
    int actualRetryCount = 0;
    var context = ResilienceContextPool.Shared.Get();
    var retry = new ResiliencePipelineBuilder<int>()
    .AddRetry(new () {
        ShouldHandle = new PredicateBuilder<int>().Handle<MyException>(),
        MaxRetryAttempts = 3,
        OnRetry = _ =>
        {
            actualRetryCount++;
            return default;
        }
    }).Build();

    //Act
    Outcome<int> actual = await retry.ExecuteOutcomeAsync<int, string>((ctx, stt) => throw new MyException(), context, "a");
    ResilienceContextPool.Shared.Return(context);

    //Assert
    Assert.Equal(expectedRetryCount, actualRetryCount);
    Assert.IsType<MyException>(actual.Exception);
}

Yes, it seems like we don't need to wrap the Exception explicitly into an Outcome.

But let's double-check this with @martintmk

from polly.

peter-csala avatar peter-csala commented on June 29, 2024

@peter-csala Your example works by luck because the retry strategy does exception handling inside. if you added other strategy (custom for example) the test would fail.

I did not know that. Thanks for calling it out!

from polly.

troygould-stack avatar troygould-stack commented on June 29, 2024

Ok, while it works by luck, we don't want to depend on the fact that its works by luck. Someone else might look at this, and do the same with a different strategy, and it doesn't work the same.

Based on the discussion, it feels like it's not a great idea to try to catch exceptions inside of the callback, and throw them as our own custom exception. Keep it simple inside the Polly pipeline? Maybe this is just a personal preference, and not really an anti-pattern.

Not so good?

            var outcome = await pipeline.ExecuteOutcomeAsync(static async (_, state) =>
            {
                try
                {
                    var httpResponseMessage = await state.HttpClient.PostAsJsonAsync(state.Path, state.Content);
                    return Outcome.FromResult(httpResponseMessage);
                }
                catch (Exception e)
                {
                    return Outcome.FromException<HttpResponseMessage>(new MyApplicationCustomException("something bad happened", e));
                }

            }, context, new { HttpClient = httpClient, Content = content, Path = purgePath });

This will interfere with the ShouldHandle method without looking at the InnerException of Outcome.Exception.

Better?

            var outcome = await pipeline.ExecuteOutcomeAsync(static async (_, state) =>
            {
                try
                {
                    var httpResponseMessage = await state.HttpClient.PostAsJsonAsync(state.Path, state.Content);
                    return Outcome.FromResult(httpResponseMessage);
                }
                catch (Exception e)
                {
                    return Outcome.FromException<HttpResponseMessage>(e);
                }

            }, context, new { HttpClient = httpClient, Content = content, Path = purgePath });

            // Deal with returning a custom application exception here instead.

from polly.

martintmk avatar martintmk commented on June 29, 2024

We can handle this on Polly level, the question is whether we want to take a perf hit @martincostello, @peter-csala ?

from polly.

martincostello avatar martincostello commented on June 29, 2024

Is there a way to make this opt-in somehow?

I think we'd only want to "spend" some of our theoretical performance budget on making the change for all usage if this is proving a common stumbling block for people.

from polly.

peter-csala avatar peter-csala commented on June 29, 2024

I think we can agree on that the current situation is sub-optimal. Asking the caller via a documentation comment to wrap the exceptions into Outcome objects is not ideal.

Even though the use of ExecuteOutcomeAsync is considered as advanced scenario I still think that every public API should be easy to use, safe and then performant. Because C# allows you to simply throw the exception rather than requiring to wrap it into an Outcome object I think it is the library's responsibility to handle this case gracefully as well.

opt-in vs opt-out

I would personally vote for opt-out instead of opt-in. The default behavior should work with throw statements as well. But when all nanoseconds and bytes matter then you can turn off the "safe guard" and let it run without the try-catch.

from polly.

github-actions avatar github-actions commented on June 29, 2024

This issue is stale because it has been open for 60 days with no activity. It will be automatically closed in 14 days if no further updates are made.

from polly.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.