-
Notifications
You must be signed in to change notification settings - Fork 835
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
Conversation
* {@link GlobalClientConfigurationDefaults}, and a lower-priority configuration than the customer-provided configuration. | ||
*/ | ||
@SdkInternalApi | ||
public class DynamoDbClientConfigurationDefaults extends ClientConfigurationDefaults { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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().
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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<>(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal or public?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Public. Will add.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Durations?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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. |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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<>(); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels internal
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
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... |
import software.amazon.awssdk.http.SdkHttpFullRequest; | ||
import software.amazon.awssdk.util.CapacityManager; | ||
|
||
public class RetryHandler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Annotate internal?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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); | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on making these default methods like Predicate does?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
d7f2b85
to
dd3c878
Compare
@Immutable | ||
public final class RetryPolicy { | ||
@SdkPublicApi | ||
public final class RetryPolicy implements ToCopyableBuilder<RetryPolicy.Builder, RetryPolicy> { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
dd3c878
to
0979960
Compare
|
||
public static final Duration MAX_BACKOFF = Duration.ofMillis(20_000); | ||
|
||
public static final Integer DEFAULT_NUM_RETRIES = 3; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
must not be negative?
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
must not be negative
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
0979960
to
17fb95e
Compare
17fb95e
to
c9679db
Compare
No description provided.