Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add default retry predicates to user provided ones #5724

Merged
merged 9 commits into from
Dec 4, 2024

Conversation

L-Applin
Copy link
Contributor

Add default retry predicates to retry strategies on top of user provided predicates.

Motivation and Context

Currently, the SDK will only add default retry predicates when user do not specified any, effectively overriding defaults whenever a single other predicate is defined by user.

Modifications

  • Added an opt out configuration: The boolean useClientDefaults() method has been added to the RetryStrategy and builder interface to check whether this strategy should add the default predicates or not.
  • Keep track of which defaults have been added to prevent adding them multiple time at client build time.
  • Moved DefaultAwsClientBuilderTest to junit 5

Testing

Added unit tests

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

… user specified predicates. Add a configuration to opt-out, to have the retry strategy use only the use user-provided predicates.
@L-Applin L-Applin requested a review from a team as a code owner November 22, 2024 17:53
@L-Applin L-Applin changed the title Olapplin/default retry predicate Add default retry predicates to user provided ones Nov 22, 2024
* Identify the Builder has having the specified set of predicate added to it.
* @param defaultPredicatesName the name of the set of predicate
*/
default void markDefaultAdded(String defaultPredicatesName) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option for design might be to model "defaults" explicitly, and make this addDefaults(RetryStrategyDefaults), so that we enforce that defaults are always added before they are marked as added. We may not need shouldAddDefaults at that point, because the "should I add this?" can be done within the retry strategy and not rely on the caller to make the proper decision.

The drawback for that is that we can't avoid the round-trip to a builder if there are no defaults to be added. Not sure what the cost of that is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right I see what you mean. The caller would just attempt to add the default every time, which would just be a noop within the retry strategy if those defaults were already added. It's not a big change, I can see the benefit of doing it this way.

default void markDefaultAdded(String defaultPredicatesName) {

}
void markDefaultAdded(String defaultPredicatesName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this method needed anymore?

Copy link
Contributor Author

@L-Applin L-Applin Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is if we want to avoid having the core retry classes reference BaseRetryStrategy like you proposed eariler, which I think is the better approach. For example, AwsRetryStrategy needs to be able to mark defaults as being added when instantiated directly, to not duplicate them when the default client builder tries to add them later at client build time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, I imagined AwsRetryStrategy using addDefaults on instantiation to ensure they're marked as already included. I can see why that's weird now. For some reason I also imagined addDefaults being on the builder, not on the retry strategy itself.

Comment on lines +27 to +30
/**
* @return The unique name that identifies this set of predicates
*/
String name();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we implement equals() and hashCode(), we don't even need name().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmmhh, Implementations of this interface have no fields, there is nothing to compare to for equals and hash code. We could make the implementations not anonymous and used their name, is that what you're proposing? For SdkDefaultRetryStrategy it would be something like:

    private static final class SdkRetryStrategyDefaults implements RetryStrategyDefaults {
        @Override
        public void applyDefaults(RetryStrategy.Builder<?, ?> builder) {
            configureStrategy(builder);
            markDefaultsAdded(builder);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            return o != null && getClass() == o.getClass();
        }

        @Override
        public int hashCode() {
            return getClass().getName().hashCode();
        }
    }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That or just use identity equality and singletons, but I suppose that names are less opaque.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I slightly prefer singleton and identity, lets got with that. We wouldn't need to implement hashCode and equals then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed my mind, lets just keep name(), it's simpler

@L-Applin L-Applin added this pull request to the merge queue Dec 4, 2024
Merged via the queue into master with commit 9f4449b Dec 4, 2024
17 checks passed
@@ -66,6 +71,8 @@ public abstract class BaseRetryStrategy implements RetryStrategy {
this.treatAsThrottling = Validate.paramNotNull(builder.treatAsThrottling, "treatAsThrottling");
this.exceptionCost = Validate.paramNotNull(builder.exceptionCost, "exceptionCost");
this.tokenBucketStore = Validate.paramNotNull(builder.tokenBucketStore, "tokenBucketStore");
this.defaultsAdded = Validate.paramNotNull(builder.defaultsAdded, "defaultsAdded");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to copy the set here instead of using the same as the builder, or, better yet, copy and pass it trough Collections.unmodifiableSet. Otherwise you end up sharing state that you shouldn't. For instance:

RetryStategy.Builder builder = standardRetryStrategy().toBuilder();
addSomeFooDefaults(builder);
RetryStategy strategyA = builder.build();
addSomeBarDefaults(builder);
RetryStategy strategyB = builder.build();

After this, both, strategyA and strategyB think they have both, Foo and Bar defaults whereas only strategyA does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants