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

Retry policy refactor #190

Merged
merged 2 commits into from
Nov 13, 2017
Merged

Retry policy refactor #190

merged 2 commits into from
Nov 13, 2017

Conversation

spfink
Copy link
Contributor

@spfink spfink commented Oct 3, 2017

No description provided.

* {@link GlobalClientConfigurationDefaults}, and a lower-priority configuration than the customer-provided configuration.
*/
@SdkInternalApi
public class DynamoDbClientConfigurationDefaults extends ClientConfigurationDefaults {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we wire this up instead of deleting it?

* @param retryCondition The retry condition
* @return This builder for method chaining
*/
Builder retryCondition(RetryCondition retryCondition);
Copy link
Contributor

Choose a reason for hiding this comment

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

You have maxNumberRetries and RetryCondition as separate things. I'd expect the maximum number of retries be part of the condition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It actually builds down to the RetryCondition but I do think it should be a separate field. Customers may want to just modify the number of retries without changing the other conditions that we retry on. This gives them an easy way to do that. If we make it required as part of the retry condition and force customers to use the AndRetryCondition its not really user friendly.

Copy link
Contributor

Choose a reason for hiding this comment

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

I have gone back and forth on this myself. On the one hand it bothers me from an abstraction point of view but I totally get that it's common for customers to want to tweak the number of retries without messing too much with the other conditions on which it retries. I wonder if we can make this easier by returning a builder similiar to https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-opensdk/src/main/java/com/amazonaws/opensdk/retry/RetryPolicyBuilder.java with all the service defaults filled in and customers can tweak that as they need it. Perhaps we can expose a method on the builder that provides them the builder to modify.

Copy link
Contributor Author

@spfink spfink Oct 11, 2017

Choose a reason for hiding this comment

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

Matt and I talked more about this offline yesterday.

Basically numRetries is the maximum amount of retries per a given request. Its specified at the top level to allow for easy configuration as it is a value that is common to change. It also allows a easy way to specify an overall max for a given request.

Within each condition, you are given the number of attempts and additional context so you can still have a condition that does less retries. You would just do that within the shouldRetry method.

We already kinda have a method for returning a builder with the defaults specified. If you just do RetryPolicy.builder().build() it will return a retry policy with all defaults and you then have a toBuilder().

Copy link
Contributor

Choose a reason for hiding this comment

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

That doesn't work with service defaults though, ideally core should define no service specific default retry (outside of connection/io related failures) and all service retryable exceptions should be wired up by the service builder. Now we may not be able to get fully there by GA but our interfaces should be forward thinking with that in mind.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can sync offline on this further. I think we are on the same page.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure thing. I'm free all day today.

import software.amazon.awssdk.RetryableException;
import software.amazon.awssdk.http.HttpStatusCodes;

public class SdkDefaultRetryPolicies {
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 a public or private class? From the name, I would have expected it to have retry policies in it.

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'll rename it to something. Couldn't really figure out a good name.

This will be public as I don't see any reason not to expose these values to customers.

public static final Set<Class> RETRYABLE_EXCEPTIONS;

static {
THROTTLING_ERROR_CODES = new HashSet<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Can these be unmodifiable sets instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. Wanted to wait until we are JDK9 compatible though to make it easy. Can add a TODO.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 I just know someone is going to modify them to define their own global retryable error codes....

CLOCK_SKEW_ERROR_CODES.add("InvalidSignatureException");
CLOCK_SKEW_ERROR_CODES.add("SignatureDoesNotMatch");
CLOCK_SKEW_ERROR_CODES.add("AuthFailure");
CLOCK_SKEW_ERROR_CODES.add("RequestInTheFuture");
Copy link
Contributor

Choose a reason for hiding this comment

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

These are kind of AWS specific. Do we have a plan for pulling them out of core?

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 I know this would expand the scope quite a bit but long term I think we want to track these via traits and generate service specific retry conditions.

Copy link
Contributor Author

@spfink spfink Oct 10, 2017

Choose a reason for hiding this comment

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

Generating retry policies per service is definitely something that we should do and I think this PR makes this easy. Each service would just build its own retry policy while taking the defaults for things that the service does not override.

I imagine always having some base set like this but we should generate custom policies per service based on some model and then wire those up in the default client builder. That's why I removed the DynamoDB one from here. They should be generated within the service module and wired up on generation rather than being predefined in core.

We should take another look at the proposed JSON retry schema and try and get that in. We don't have to do that now though and I don't believe we will be blocked from doing so. It might mean the retry policy changes for a service but I don't think that is not backwards compatible as it should be additive from the default policy.

import software.amazon.awssdk.retry.conditions.MaxNumberOfRetriesCondition;
import software.amazon.awssdk.retry.conditions.RetryCondition;

public class SimpleRetryPolicy implements RetryPolicy {
Copy link
Contributor

Choose a reason for hiding this comment

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

internal or public?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Public. Will add.

Copy link
Contributor

Choose a reason for hiding this comment

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

Final

SdkDefaultRetryPolicies.DEFAULT_NUM_RETRIES);

BackoffStrategy NONE = new FixedDelayBackoffStrategy(1);

/**
* Compute the delay before the next retry request. This strategy is only consulted when there will be a next retry.
*
* @param context Context about the state of the last request and information about the number of requests made.
* @return Amount of time in milliseconds to wait before the next attempt. Must be non-negative (can be zero).
*/
long computeDelayBeforeNextRetry(RetryPolicyContext context);
Copy link
Contributor

Choose a reason for hiding this comment

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

Would Duration be better than long here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah that should be fine. Will update.

import java.util.Random;
import software.amazon.awssdk.retry.RetryPolicyContext;

public class EqualJitterBackoffStrategy implements BackoffStrategy {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we comment what these different strategies do? Customers might want to use them, but have a bit more of an explanation.

private final Random random = new Random();

public EqualJitterBackoffStrategy(final int baseDelay,
final int maxBackoffTime,
Copy link
Contributor

Choose a reason for hiding this comment

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

Durations?

Copy link
Contributor

Choose a reason for hiding this comment

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

Should this have a builder. Hard to know what these ints are without reading documentation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ping

@@ -25,7 +25,7 @@
private final int maxNumberOfRetries;

public MaxNumberOfRetriesCondition(int maxNumberOfRetries) {
this.maxNumberOfRetries = assertIsPositive(maxNumberOfRetries, "maxNumberOfRetries");
this.maxNumberOfRetries = maxNumberOfRetries;
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we okay with negatives now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. We can add this back but not sure we need to. Anything <= 0 will just be 0 retries.

Copy link
Contributor

Choose a reason for hiding this comment

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

meh I'd rather not do that. Lots of contracts use -1 or something to mean infinite and I'd rather not open ourselves up to that confusion. We can always relax constraints in the future if there is a need.

Copy link
Contributor

@shorea shorea left a comment

Choose a reason for hiding this comment

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

Overall looks really good :)

@@ -59,8 +58,7 @@ public Object originalRequest() {
}

/**
* @return The marshalled request. See {@link Request#addHandlerContext(HandlerContextKey, Object)} for a mechanism to store
* request level state across invocations of the retry policy.
* @return The marshalled request.
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we include ExecutionContext in here so they have somewhere to store mutable state?

CLOCK_SKEW_ERROR_CODES.add("InvalidSignatureException");
CLOCK_SKEW_ERROR_CODES.add("SignatureDoesNotMatch");
CLOCK_SKEW_ERROR_CODES.add("AuthFailure");
CLOCK_SKEW_ERROR_CODES.add("RequestInTheFuture");
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 I know this would expand the scope quite a bit but long term I think we want to track these via traits and generate service specific retry conditions.

public static final Set<Class> RETRYABLE_EXCEPTIONS;

static {
THROTTLING_ERROR_CODES = new HashSet<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 I just know someone is going to modify them to define their own global retryable error codes....

import software.amazon.awssdk.retry.conditions.MaxNumberOfRetriesCondition;
import software.amazon.awssdk.retry.conditions.RetryCondition;

public class SimpleRetryPolicy implements RetryPolicy {
Copy link
Contributor

Choose a reason for hiding this comment

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

Final

private final Random random = new Random();

public EqualJitterBackoffStrategy(final int baseDelay,
final int maxBackoffTime,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this have a builder. Hard to know what these ints are without reading documentation.

@@ -25,7 +25,7 @@
private final int maxNumberOfRetries;

public MaxNumberOfRetriesCondition(int maxNumberOfRetries) {
this.maxNumberOfRetries = assertIsPositive(maxNumberOfRetries, "maxNumberOfRetries");
this.maxNumberOfRetries = maxNumberOfRetries;
Copy link
Contributor

Choose a reason for hiding this comment

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

meh I'd rather not do that. Lots of contracts use -1 or something to mean infinite and I'd rather not open ourselves up to that confusion. We can always relax constraints in the future if there is a need.


private final Set<String> retryableErrorCodes;

public RetryOnErrorCodeCondition(Set<String> retryableErrorCodes) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is public perhaps a var arg for convenience.

/**
* Precanned instances of {@link RetryPolicyContext} and factory methods for creating contexts.
*/
public class RetryPolicyContexts {
Copy link
Contributor

Choose a reason for hiding this comment

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

Feels internal

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is in the test directory.

*/
public static final RetryPolicyContext EMPTY = RetryPolicyContext.builder().build();

public static final RetryPolicyContext LEGACY = RetryPolicyContexts.fromLegacy(
Copy link
Contributor

Choose a reason for hiding this comment

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

What's this for?

* @param retryCondition The retry condition
* @return This builder for method chaining
*/
Builder retryCondition(RetryCondition retryCondition);
Copy link
Contributor

Choose a reason for hiding this comment

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

I have gone back and forth on this myself. On the one hand it bothers me from an abstraction point of view but I totally get that it's common for customers to want to tweak the number of retries without messing too much with the other conditions on which it retries. I wonder if we can make this easier by returning a builder similiar to https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-opensdk/src/main/java/com/amazonaws/opensdk/retry/RetryPolicyBuilder.java with all the service defaults filled in and customers can tweak that as they need it. Perhaps we can expose a method on the builder that provides them the builder to modify.

@spfink
Copy link
Contributor Author

spfink commented Oct 11, 2017

Updated per feedback.

Only thing that I haven't done yet from the feedback is the builders on the backoff strategies. Typically you would see the builder on the interface but that doesn't work here. I guess I can add one specific to each strategy...

@shorea

import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.util.CapacityManager;

public class RetryHandler {
Copy link
Contributor

Choose a reason for hiding this comment

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

Annotate internal?

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'd lean more towards public. The logic here shouldn't ever change as it mostly delegates out to the retry policy. If customers want to use this to determine whether or not a given request should be retried I think that's fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

How would the customer use this?

public RetryHandler(RetryPolicy retryPolicy,
CapacityManager retryCapacity) {
this.retryPolicy = retryPolicy;
this.retryCapacity = retryCapacity;
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible for the capacity manager to be part of the retry condition as well? Essentially, you mix-in throttling, the same way we're mixing in the max-retry?

We'd probably need the extra hooks to release the capacity, but it seems weird that an SDK customer can't implement retry throttling using our condition interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It could but I'm not sure it's worth the extra complexity. This is always the first condition that should be checked as you'd be wasting time if it wasn't. We'd also have to help a lot through our builder as the customer shouldn't need to know which exceptions are throttling if they want to override the defaults.

Copy link
Contributor

Choose a reason for hiding this comment

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

The complexity argument is legit - I'm not sure how much extra it would be. Would it be more than 1 method?

The wasting-time argument I don't follow so much. What's another few nanoseconds added to the case that the throttle condition will fail?

In terms of the customer not knowing about which exceptions are throttling exceptions, the same thing applies to the customer not knowing which exceptions are retryable exceptions. If that's a problem here, it's a problem there as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In terms of the customer not knowing about which exceptions are throttling exceptions, the same thing applies to the customer not knowing which exceptions are retryable exceptions.

I don't think that's equivalent. In the retryable exception case, there is one dimension, exceptions. They either take ours or set their own.

In a condition dealing with throttling they are more likely interested in modifying the number of throttled retries and the cost while using our throttled retries. In that case they need to know what our throttled exceptions are to be able to produce a complete retry condition.

The wasting-time argument I don't follow so much. What's another few nanoseconds added to the case that the throttle condition will fail?

Ultimately I guess it could apply in any situation where you have a set of conditions but I would typically see a priority to retry conditions in which throttling is the highest. Evaluating any condition before it would be a waste of time.

Copy link
Contributor

Choose a reason for hiding this comment

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

It just feels like our retry policy interface isn't powerful enough to express what we need, so we're working around it to add extra features. It feels like LESS complexity to have it under just one abstraction. I suppose it's always something we could add later, assuming we don't expose the concept of capacity manager outside of the core.

Would a reasonable compromise be to hide all traces of it from our public interfaces?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm on the fence. I see where you're coming from Matt and I agree it would make sense in the retry condition. We would need some way to notify the retry condition that a request has succeeded though and I feel like that would pollute the interface a bit (no longer a simple predicate). Not exactly sure how many customers would actually want to implement their own throttling so I'd say lean towards leaving it out for now.

public BackoffStrategy getBackoffStrategy() {
return backoffStrategy;
@SdkPublicApi
public interface RetryPolicy extends BackoffStrategy, RetryCondition, ToCopyableBuilder<RetryPolicy.Builder, RetryPolicy> {
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems weird that a retry policy is-a backoff strategy and a retry condition. That seems like a has-a relationship. Any particular reason to extend the interfaces here?

Copy link
Contributor

Choose a reason for hiding this comment

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

I like it extending the interfaces, otherwise we have to write awkward code in the runtime to get the individual strategies. Also let's you implement the condition and backoff strategy easily in one class.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could always implement both in the same class if we wanted by making the subclass implement the condition and backoff strategy interfaces. What's the awkward code we'd have to write in the runtime?

Here's my complaint about the way it currently works:

Given:

class MyRetryPolicy implements RetryPolicy {
...
}

We can create a retry policy that delegates to other retry policies, which to me is a bit odd:

RetryPolicy myRetryStrategy = new MyRetryPolicy();
RetryPolicy letsGetMeta = RetryPolicy.builder()
    .retryCondition(myRetryStrategy)
    .backoffStrategy(myRetryStrategy)
    .build();

Even if that's considered a "feature", it implies a retry policy must always delegate to a retry condition and backoff strategy, but it also IS a retry condition and backoff strategy? That has really weird implications.

For example, I can't even wrap my head around what this code does:

RetryPolicy whatDoesThisEvenDoPolicy = myRetryStrategy.toBuilder()
    .retryCondition(myRetryStrategy)
    .backoffStrategy(myRetryStrategy)
    .build();

MyRetryPolicy must implement toBuilder(), because it extends ToCopyableBuilder, but it also must HAVE a condition and a backoff strategy because it can be converted to and from a builder that HAS one? So RetryPolicy always delegates to a RetryCondition and a BackoffStrategy?

My only guess at how to implement by own retry policy is to either throw a UnsupportedOperationException from toBuilder (meaning we've given up on the L in SOLID) or just make my retry policy always delegate to a condition and backoff strategy, at which point every RetryPolicy is a decorator (meaning we've given up on the I in SOLID). We will have already given up on the S in SOLID by having the class implement both interfaces.

Copy link
Contributor

Choose a reason for hiding this comment

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

So RetryPolicy always delegates to a RetryCondition and a BackoffStrategy?

Not necesarily. The simple implementation takes a condition and backoff strategy separately and delegates to them but you could just as easily implement it all in one class and combine the logic. I think the weirdness here stems from forcing the builder on the retry policy interface, do we even need a builder? Would a factory method suffice for the common case of combining condition and strategy?

Copy link
Contributor

@millems millems Oct 18, 2017

Choose a reason for hiding this comment

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

If we don't make it ToCopyableBuilder, it's better, but we're still breaking the S and I in SOLID and I'm not sure what benefit it's bringing us.

retryPolicy.retryCondition().shouldRetry(context) is pretty much identical to retryPolicy.shouldRetry(context).

Copy link
Contributor

Choose a reason for hiding this comment

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

How does it break SOLID?

Copy link
Contributor

Choose a reason for hiding this comment

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

S: We're defining an interface RetryPolicy with two potential responsibilities: defining a condition and defining a backoff strategy.
I: We have an interface RetryPolicy that has two methods that can (and already are in this case) be separated into two separate interfaces.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't really agree with all that. In certainly can be broken up into two separate concepts but from a customer's perspective its one logical thing, how the client handles retry situations. Does any aggregate interface break SOLID? I wouldn't think so.

@@ -121,6 +131,11 @@ public Builder exception(SdkBaseException exception) {
return this;
}

public Builder executionContext(ExecutionContext executionContext) {
Copy link
Contributor

Choose a reason for hiding this comment

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

ExecutionContext feels like a big, internal thing to expose to a public interface like the retry policy. Would it be better to expose the ExecutionAttributes, which is what we currently expose to the interceptors for the same reason?

Copy link
Contributor

Choose a reason for hiding this comment

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

+1, sorry that was my suggestion and got the names mixed up.

import software.amazon.awssdk.http.HttpStatusCodes;

// TODO: Add some JDK9 sweetness to this class when ready
public class SdkDefaultRetrySettings {
Copy link
Contributor

Choose a reason for hiding this comment

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

Annotate as internal or public? It feels like something we might want to change, so I'd vote for internal...

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'm fine with internal in that we can change it but I don't like the full description of internal for this. I'm fine with customers using / depending on this to get at what we are retrying as long as they understand it will change.

Copy link
Contributor

Choose a reason for hiding this comment

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

Internal for now and make it public in the future if we need to? Less surface area to worry about...


/**
* Super interface for {@link RetryPolicy} that defines a strategy for backing off between retries.
*/
public interface BackoffStrategy {
Copy link
Contributor

Choose a reason for hiding this comment

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

Annotate as public?


import static software.amazon.awssdk.util.ValidationUtils.assertIsPositive;
import static software.amazon.awssdk.utils.NumericUtils.assertIsPositive;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be in Validate with our other validation methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe? We had this NumericUtils class that already had a few validations for number types. Makes sense to me that NumericUtils should be used here but not a big deal to me either way.

Copy link
Contributor

Choose a reason for hiding this comment

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

Validate already has some stuff for different types. We should decide on one place. Should we move string validation to StringUtils?


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import software.amazon.awssdk.retry.RetryPolicyContext;
Copy link
Contributor

Choose a reason for hiding this comment

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

Needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, it was just an unchanged file with an added import.

import software.amazon.awssdk.AmazonServiceException;
import software.amazon.awssdk.retry.RetryPolicyContext;

public class RetryOnErrorCodeCondition implements RetryCondition {
Copy link
Contributor

Choose a reason for hiding this comment

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

Internal or public?

// TODO: Switch to varargs and Set.of()
public RetryOnErrorCodeCondition(Set<String> retryableErrorCodes) {
this.retryableErrorCodes = new HashSet<>(retryableErrorCodes);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

With Java 9 will we need varargs? Set.of(...) isn't much harder, and it makes it clear that there's no duplicates allowed. I don't have a strong opinion, but there are a few other conditions that take Sets in this review, so we should consider moving this TODO to them as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A little less burden on the customer. Fine either way though.

// Do not use retry capacity for throttling exceptions
if (!RetryUtils.isThrottlingException(exception)) {
// See if we have enough available retry capacity to be able to execute this retry attempt.
if (!retryCapacity.acquire(SdkDefaultRetrySettings.THROTTLED_RETRY_COST)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This confused me at first. I was thinking the cost was specific to throttling exceptions but we don't acquire for throttling exceptions. I see now it's just the default cost for "retry throttling" but the naming really confused me. How about RETRY_THROTTLING_COST?


public static final Duration BASE_DELAY = Duration.ofMillis(100);

public static final Duration MAX_BACKOFF_IN_MILLIS = Duration.ofMillis(20_000);
Copy link
Contributor

Choose a reason for hiding this comment

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

IN_MILLIS doesn't make sense in the name now.

public static final int THROTTLED_RETRY_COST = 5;

/**
* When throttled retries are enabled, this is the total number of subsequent failed retries
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment seems misleading. If each retry consumes 5 cost then there would be only 20 retries before capacity is drained right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Capacity is THROTTLED_RETRIES * THROTTLED_RETRY_COST

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahhh makes sense.

public interface RetryCondition {

RetryCondition DEFAULT = new OrRetryCondition(new RetryOnStatusCodeCondition(SdkDefaultRetrySettings.RETRYABLE_STATUS_CODES),
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fine with adding and/or default methods for any given two retry conditions but not sure it makes sense in the line you are highlighting here.

Think it makes sense to do one condition and another condition. Don't think it makes sense to do a condition and a list of others. You can do retryCondition and RetryCondition and RetryCondition but then you are needlessly adding layers.

return num;
}

public static int assertIsPositive(int num, String fieldName, boolean zeroIsPositive) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we just have another method name assertIsNonNegative? Not a fan of flags to change method behavior.

@Immutable
public final class RetryPolicy {
@SdkPublicApi
public final class RetryPolicy implements ToCopyableBuilder<RetryPolicy.Builder, RetryPolicy> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Immutable?


@Override
public Builder toBuilder() {
return builder().numRetries(numRetries).retryCondition(retryCondition).backoffStrategy(backoffStrategy);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be the original retry condition?

// 3 Retries
RetryPolicy foo = RetryPolicy.builder().maxRetries(3).build();

// Still 3 retries?
RetryPolicy bar = foo.toBuilder().maxRetries(10).build();

long delayBeforeNextRetry(AmazonWebServiceRequest originalRequest,
AmazonClientException exception,
int retriesAttempted);
private Integer numRetries;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: Should these start off assigned with the default values instead of binding them in the constructor?

Right now:

RetryPolicy.Builder foo = RetryPolicy.builder();

// null
foo.numRetries();

// 3
foo.build().toBuilder().numRetries();

*/
public ExecutionContext executionContext() {
return this.executionContext;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Execution attributes? Execution context is pretty internal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Weird, I swear I had already done this. Must have gotten lost somewhere.

private final int retriesAttempted;
private final Integer httpStatusCode;

private RetryPolicyContext(Object originalRequest,
SdkHttpFullRequest request,
SdkBaseException exception,
ExecutionContext executionContext,
int retriesAttempted,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should RetryPolicyContext be final?


package software.amazon.awssdk.core.retry.backoff;

import static software.amazon.awssdk.utils.NumericUtils.assertIsNotNegative;
Copy link
Contributor

Choose a reason for hiding this comment

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

Where did we land on the location for this? Validate already has some for numbers, so we should unify the location. I vote for Validate as all of our validation stuff.


public FullJitterBackoffStrategy(final Duration baseDelay,
final Duration maxBackoffTime,
final int numRetries) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment here. Builder?

import software.amazon.awssdk.core.retry.RetryPolicyContext;
import software.amazon.awssdk.core.retry.SdkDefaultRetrySettings;

@SdkPublicApi
public interface RetryCondition {
Copy link
Contributor

Choose a reason for hiding this comment

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

FunctionalInterface

public class RetryOnExceptionsCondition implements RetryCondition {

private final List<Class<? extends Exception>> exceptionsToRetryOn;
private final Set<Class> exceptionsToRetryOn;
Copy link
Contributor

Choose a reason for hiding this comment

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

Class<?>? Raw use of generic types is gross...

@@ -24,6 +24,7 @@
import java.io.IOException;
import java.util.Iterator;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.BasicConfigurator;
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 used here? There was no content changes.


public static final Duration MAX_BACKOFF = Duration.ofMillis(20_000);

public static final Integer DEFAULT_NUM_RETRIES = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

DEFAULT_RETRY_COUNT or DEFAULT_MAX_RETRIES?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fine with DEFAULT_MAX_RETRIES


default int calculateExponentialDelay(int retriesAttempted, Duration baseDelay, Duration maxBackoffTime, int maxRetries) {
int retries = Math.min(retriesAttempted, maxRetries);
return (int) Math.min((1L << retries) * baseDelay.toMillis(), maxBackoffTime.toMillis());
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't remember how this conversion works. If the computed value can always be fit in an int, we can use Math.toIntExact(long).

};
private static final RetryPolicy RETRY_POLICY = RetryPolicy.builder()
.retryCondition((c) -> false)
.backoffStrategy((c) -> Duration.ZERO)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need the extra paranthesis around c? Also better naming for c?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope and sure

public static int assertIsNotNegative(int num, String fieldName) {

if (num < 0) {
throw new IllegalArgumentException(String.format("%s must be positive", fieldName));
Copy link
Contributor

Choose a reason for hiding this comment

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

must not be negative?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure

}

if (duration.isNegative()) {
throw new IllegalArgumentException(String.format("%s must be positive", fieldName));
Copy link
Contributor

Choose a reason for hiding this comment

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

must not be negative

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure

@spfink spfink merged commit 65b6517 into master Nov 13, 2017
@spfink spfink deleted the finks/retry-policy branch December 6, 2017 18:00
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.

4 participants