question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Observations about v8 API

See original GitHub issue

I’ve started to scrutinize the V8 samples and I have found the following things:

Readme

The links are broken. They are pointing to the wrong places.

Intro

strategy = new ResilienceStrategyBuilder()
    // Add retries using the options
    .AddRetry(new RetryStrategyOptions
    {
        ShouldRetry = outcome =>
        {
            // We want to retry on this specific exception
            if (outcome.Exception is TimeoutRejectedException)
            {
                // The "PredicateResult.True" is shorthand to "new ValueTask<bool>(true)"
                return PredicateResult.True;
            }

            return PredicateResult.False;
        },

This ShouldRetry feels a bit clumsy. IMHO it should be as simple as this:

ShouldRetry = outcome => outcome.Exception is TimeoutRejectedException,

The shortest version which can be used now still feels clumsy a bit

ShouldRetry = outcome => new ValueTask<bool>(outcome.Exception is TimeoutRejectedException),

GenericStrategies

ResilienceStrategy<HttpResponseMessage> strategy = new ResilienceStrategyBuilder<HttpResponseMessage>()
    .AddFallback(new FallbackStrategyOptions<HttpResponseMessage>
    {
        FallbackAction = async _ =>
        {
            await Task.Delay(10);

            // Return fallback result
            return new Outcome<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.OK));
        },

If I want to simply return with 200 (without any async op) then I still have to write this code

FallbackAction =  _ => new ValueTask<Outcome<HttpResponseMessage>>(new Outcome<HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.OK))),

Another thing is that the AddTimeout does not have any impact on the to-be-decorated method in the example.
For example if I increase the Task.Delay(10) to Task.Delay(10_000) still the timeout never triggers.
By passing the token to the Task.Delay causes a timeout.

I would suggest to either remove the Timeout policy or fix the example to work with timeout scenario as well.

Retries

Based on the samples I can define a retry strategy like this:

strategy = new ResilienceStrategyBuilder()
    .AddRetry(new RetryStrategyOptions
    {
        ShouldRetry = outcome => new ValueTask<bool>(outcome.Exception is InvalidOperationException),
        RetryCount = 4,
        RetryDelayGenerator = outcome => new ValueTask<TimeSpan>(TimeSpan.FromSeconds(outcome.Arguments.Attempt)),
        BaseDelay = TimeSpan.FromSeconds(10),
        BackoffType = RetryBackoffType.Constant, //or Exponential or any value
        OnRetry = outcome =>
        {
            Console.WriteLine($"Retrying delay: {outcome.Arguments.RetryDelay}");
            return default;
        }
    })
    .Build();

strategy.Execute(() => throw new InvalidOperationException());

It does compile and it does perform retries. In the above example the BaseDelay and BackoffType values are ignored.

Extensibility

I do understand the intend to do not expose the strategy itself rather just its options.
Still it feels like you have to write a lots of code to make it work.

It feels a bit weird for me that one of the overloads of the AddStrategy returns with a ResilienceStrategyBuilder, but the other overload returns void. In the former case you have to call .Build in the latter you can’t.

Also IMHO the ResilienceStrategyBuilder should allow just a single strategy registration. But currently you can register as many you want. What is the use case where you would need multiple strategies?

var strategy = new ResilienceStrategyBuilder()
    .AddStrategy(new MySimpleStrategy())
    .AddMyResilienceStrategy(new MyResilienceStrategyOptions
    {
        OnCustomEvent = args => { Console.WriteLine("OnCustomEvent"); return default; }
    })
    .AddStrategy(new MySimpleStrategy())
    .Build();

DependencyInjection

var serviceProvider = new ServiceCollection()
    .AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug))
    .AddResilienceStrategy("my-strategy", (builder, context) =>
    {
        context.ServiceProvider.GetRequiredService<ILoggerFactory>();
        builder.AddTimeout(TimeSpan.FromSeconds(1));
    })
    .AddResilienceStrategy<string, HttpResponseMessage>("my-strategy", builder =>
    {
        builder.AddTimeout(TimeSpan.FromSeconds(1));
    })
    .BuildServiceProvider();

Why can I register multiple strategies with same name/key? Even I can register the exact same strategy multiple times

var serviceProvider = new ServiceCollection()
    .AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug))
    .AddResilienceStrategy("my-strategy", (builder, context) =>
    {
        context.ServiceProvider.GetRequiredService<ILoggerFactory>();
        builder.AddTimeout(TimeSpan.FromSeconds(1));
    })
    .AddResilienceStrategy("my-strategy", (builder, context) =>
    {
        context.ServiceProvider.GetRequiredService<ILoggerFactory>();
        builder.AddTimeout(TimeSpan.FromSeconds(1));
    })
    .BuildServiceProvider();

Why the strategy have a type like this: Polly.Utils.ResilienceStrategyPipeline, whereas the genericStrategy is Polly.ResilienceStrategy<HttpResponseMessage>?

variable types

Issue Analytics

  • State:closed
  • Created 3 months ago
  • Reactions:1
  • Comments:9 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
PeterCsalaHbocommented, Jun 23, 2023

Hey @martintmk, I’ve just checked it and yes you have addressed almost all of them (except the naming of ResilienceStrategyBuilder). Thank you Sir! 😃

1reaction
martintmkcommented, Jun 19, 2023

Hey @PeterCsalaHbo

Thanks for your feedback!

Intro

This ShouldRetry feels a bit clumsy. IMHO it should be as simple as this: ShouldRetry = outcome => outcome.Exception is TimeoutRejectedException,

Polly V8 is from async ground-up, this also makes all predicates asynchronous as well. There are scenarios where you want to determine whether to retry or not by buffering the HTTP response body which is an async operation.

We are thinking of simplifying the setup by using PredicateBuilder.

What do you think about this syntax?

new ResilienceStrategyBuilder().AddRetry(new RetryStrategyOptions
{
}.WithShouldRetry(exception => exception is TimeoutRejectedException)));

Basically, WithShouldRetry would be just convenience extension for RetryStrategyOptions that allows simplified configuration of predicates.

If I want to simply return with 200 (without any async op) then I still have to write this code

GenericStrategies

I think the same thing as before, we can simplify this by exposing convenience extensions for either FallbackStrategyOptions<HttpResponseMessage> or some additional fallback extensions for ResilienceStrategyBuilder

Actually, at some point we had some fluent API for building the predicates the easy way. However, it was deemed to complex for what it does and dropped in favor of simple delegates.

Callback API: #1067 Move to delegates : #1211

Extensibility

I do understand the intend to do not expose the strategy itself rather just its options. Still, it feels like you have to write a lots of code to make it work.

It’s the recommended approach, you can still just inline the strategy for simple scenarios (or make it public). But official strategies are internal and kept as implementation detail. This reduces the overal API surface. The actual configuration of strategies happens through extensions and options.

It feels a bit weird for me that one of the overloads of the AddStrategy returns with a ResilienceStrategyBuilder, but the other overload returns void. In the former case you have to call .Build in the latter you can’t.

The problem is that AddStrategy that returns void is an instance member, while the other is extension. However, I think we can make it work if we turn it to extension too.

Also IMHO the ResilienceStrategyBuilder should allow just a single strategy registration. But currently you can register as many you want. What is the use case where you would need multiple strategies?

This was one of the core ideas behind V8, there are no individual strategies anymore, everything is ResilienceStrategy and you don’t care of how many strategies is it composed of. The configuration of pipeline is done during the setup of ResilienceStrategyBuilder. (it’s basically the V7 policy wrap, but it’s now built-in).

We also promote the strategy composition as for any serious resilience patterns you have more than one strategy in the pipeline.

Note that if you add only a single strategy to the builder the returning instance will be the same as the one you added to it:

MyStrategy strategy = (MyStrategy)new ResilienceStrategyBuilder()
    .AddStrategy(new MyStrategy())
    .Build();

DependencyInjection

Why can I register multiple strategies with same name/key? Even I can register the exact same strategy multiple times

This enables the extensibility and customization. Basically, the last added strategy wins and overwrites the previous one. This allows customization of resilience strategies in the libraries even if they don’t expose dedicated API for customization. Open to ideas here.

Thanks again for the feedback, I can address some of those soon 😃

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation
This document introduces you to V8, while the remaining documentation shows you how to use V8 in your code and describes some of...
Read more >
Getting started with embedding V8
The V8 API provides functions for compiling and executing scripts, accessing C++ methods and data structures, handling errors, and enabling security checks.
Read more >
V8 API Reference Guide
v8 : V8 API Reference Guide. V8 is Google's open source JavaScript engine. This set of documents provides reference material generated from the...
Read more >
V8 | Node.js v20.5.1 Documentation
Returns an integer representing a version tag derived from the V8 version, command-line flags, and detected CPU features. This is useful for determining...
Read more >
Using v8 API
Tripadvisor's v8 API is described as a Swagger/OpenAPI specification file. Swagger comes with tools to autogenerate the model of the API into multiple...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found