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
Dynamic delay #111
Dynamic delay #111
Conversation
Wrap checked exceptions as unchecked in applying delay function.
if (dynamicDelay != null && dynamicDelay.toNanos() >= 0) | ||
delayNanos = dynamicDelay.toNanos(); | ||
} catch (Exception ex) { | ||
if (ex instanceof RuntimeException) |
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.
Could use a separate catch clause to get rid of the instanceof check:
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeExeption("..", e);
}
* Returns the function that determines the next delay given | ||
* a failed attempt with the given {@link Throwable}. | ||
*/ | ||
public CheckedBiFunction<Object, Throwable, Duration> getDelayFunction() { |
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.
Have you considered to give this function its own interface? E.g.
@FunctionalInterface
public interface DelayFunction {
@Nullable
Duration calculateDelay(@Nullable Object result, @Nullable Throwable exception);
}
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 the idea here that DelayFunction
is more friendly for Java 6/7 users who can't use lambdas than writing CheckedBiFunction<Object, Throwable, Duration>
?
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, that's a good reason, but also: I can't see a compelling need for a delay function to be able to throw a checked exception. I only initially used CheckedBiFunction
because there was no BiFunction
in net.jodah.failsafe.function
. When @whiskeysierra pointed out that rolling a separate @FunctionalInterface
would give us an unchecked signature, along with the benefits of a more specific method name and a place to put documentation, I embraced that right away.
All good comments. Waiting to hear from others whether this PR is really worth it. If so, will integrate these suggestions. |
Removed test of throwing checked exception from delay function, since delay functions no longer throw checked exceptions.
@whiskeysierra - all your suggestions incorporated (although the second one obviated the need for the first). |
Would love to have that PR merged. I see the most obvious use case for doing "correct" retry based on rate limiting response headers. |
I'd argue that a proper interface should be the default from a design
perspective. It gives you speaking names, for the interface and the method
as well as a place where you can put documentation.
…On Oct 12, 2017 07:38, "Jonathan Halterman" ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In src/main/java/net/jodah/failsafe/RetryPolicy.java
<#111 (comment)>:
> @@ -227,6 +229,14 @@ public Duration getDelay() {
}
/**
+ * Returns the function that determines the next delay given
+ * a failed attempt with the given ***@***.*** Throwable}.
+ */
+ public CheckedBiFunction<Object, Throwable, Duration> getDelayFunction() {
Is the idea here that DelayFunction is more friendly for Java 6/7 users
than CheckedBiFunction?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#111 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAaPnUxhSA6p8tFu-1g30A3BR2byX4f-ks5sraXXgaJpZM4Pps46>
.
|
This is good stuff. I like the use case and the simple solution.
I know :) One of these days we can sever all pre-Java 8 compatibility (at which point we can replace some of the anonymous classes with lambdas), but we have to stay friendly for our pre-Java 8 users. A few other comments:
|
It would be awesome if we could allow to use delay function and backoff in
conjunction, e.g. by falling back to backoff if no delay was calculated.
…On Oct 12, 2017 07:49, "Jonathan Halterman" ***@***.***> wrote:
I used net.jodah.failsafe.util.Duration in signature of the delay
function, though
I really wanted to use java.time.Duration
I know :) One of these days we can sever all pre-Java 8 compatibility (at
which point we can replace some of the anonymous classes with lambdas), but
we have to stay friendly for our pre-Java 8 users. A few other comments:
- Can (/should) we just call it RetryPolicy.withDelay? I'm a fan of
short names and the parameters indicate that the user must compute a delay
here.
- Do we want to be strict and throw IllegalStateException if a
delayFunction is already configured and a user calls withDelay or
withBackoff, and visa versa?
- Right now, Failsafe is used in Java 6/7 applications. The way this
works is that 6/7 users simply shouldn't use any of the APIs that would
attempt to load a Java 8 class such as the .future stuff, otherwise
everything else just works. I am wondering if the inclusion of the
@FunctionalInterface annotation would cause any problems for 6/7 users.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#111 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAaPndDTYPc26MBfKE8sl1wvZyaMMzL6ks5srahogaJpZM4Pps46>
.
|
Duration dynamicDelay = delayFunction.calculateDelay(result, failure); | ||
if (dynamicDelay != null && dynamicDelay.toNanos() >= 0) | ||
delayNanos = dynamicDelay.toNanos(); | ||
} | ||
if (delayNanos == -1) |
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 want to allow the backoff and delayFunction
features to be combined? I'm thinking no - we just let the delayFunction
do all of the work, in which case I think this could be an else if
.
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.
Sounds good to me.
@whiskeysierra I just left a comment wondering about that. Are you thinking if It might be awkward for a delayFunction's result to be altered by a backoff adjustment - the two could wind up battling each other (backoff adjustment lowers the waitTime, delayFunction might increase it again) which has me wondering - is there a use case for delay + delayFunction? |
@jhalterman My primary use case is the |
@whiskeysierra @jhalterman - If I'm following correctly, it sounds like everyone is in favor of having |
I agree. If the delay function calculates something, I believe it should be taken as is. |
Funny, I was just talking of Josh Bloch (of Effective Java fame) yesterday, and he reminded me not to
I like your suggestion of using negative delay function return values as a signal to revert to regular delay/backoff.
(You mean Answered by previous: Let negative computed delays mean "revert to regular delay/backoff". To implement this, we would need a separate variable to hold the final delay value to use for this retry, so we don't trash the
Elsewhere I said that I can't find a compelling reason for I think the right behavior is to let unexpected exceptions break the whole For example, if my attempt to read the
Oh, yes, that does make sense. Another argument to (If so, that's more grist for the custom functional interface mill.)
I would have thought it wouldn't be a problem, but I notice that I'll find out. |
I believe that the JVM will just silently ignore annotations that are not on the classpath. We should be fine here. |
@whiskeysierra - I think you're right. Here's some supporting evidence: https://stackoverflow.com/a/3567969 |
Just realized,
Yea, this one is borderline, but we already overload other parts of the API with functional things (retryOn, abortOn), so I'd prefer the overload here and just call it
Cool - let's document < 0 as a distinguished return value that falls back to the
Good point re: fail fast. We could still fail on errors such as Either way, I think we can add a
If we do propagate instead of ignore, we'll want to test that the propagation works for async executions as well.
Yea - we should include ExecutionContext because the attempt count could for people to compute their own backoff. We do actually have a type for this, |
The whole point of checked exceptions is to make it hard for the programmer to forget about handling a predictable exceptional condition. If we added |
On the subject of overloading |
@jhalterman wrote:
It's not so easy to do this, because the type of the delay function field on the RetryPolicy has to be something like That means that The best we can do is provide a Similarly with the failure type: We'd need another overloading to include a restriction on what kinds of failures the delay function is interested in. I'm going to check in modifications to include these overloadings, for now without changing the method names from |
add overload with delay function and failure type.
I did the method renaming that @jhalterman asked for, realizing that the I don't think there's a need for variants that just take a result type or a failure type, since the typical use will have to mention the expected types explicitly anyway in the lambda, e.g., RetryPolicy retryPolicy = new RetryPolicy()
.withDelay((SpecificResult result, SpecificException failure, ExecutionContext context) -> ...,
SpecificResult.class, SpecificException.class)
... So I removed those, leaving just RetryPolicy withDelay(DelayFunction<?, ? extends Throwable> delayFunction)
// and
<R, F extends Throwable> RetryPolicy withDelay(
DelayFunction<R, F> delayFunction, Class<R> resultType, Class<F> failureType) |
* {@code failureType} is null | ||
* @throws IllegalStateException if backoff delays have already been set | ||
*/ | ||
public <R, F extends Throwable> RetryPolicy withDelay(DelayFunction<R, F> delayFunction, |
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.
Coming back to this PR after too-long (apologies) and I like it overall except for this method signature, which seems a bit too prescriptive for certain cases and awkward for others, ex:
rp.withDelay(delayFn, Object.class, TooManyRequestsException.class);
Foremost, I'm wondering about the use case for this, where we'd care about the result and exception types together. Most likely we only care about one or the other. So with that in mind, along with consistency with the rest of the API, how about:
rp.withDelayOn(delayFunction, TooManyRequestsException.class); // delay on specific failure
// or
rp.withDelayWhen(delayFunction, 500); // delay on specific result
...where the use of the "On" and "When" wording indicates that the delay is conditional. Of course all of these are just convenience methods since withDelay(DelayFunction)
can always be used by itself.
Thoughts?
As part of this, we also may consider whether to expose the delay result and failure type or if we should just add a canApplyDelayFunction
to RetryPolicy
which, similar to canRetry
and canAbort
would evaluate the conditionals internally for some result/failure.
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.
Looks like you went ahead and merged. I haven't had a chance to respond to your last comment, though.
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.
No worries. I wasn't sure if you were tied up (which I can sympathize with) so I decided to just merge the PR as is and make the tweaks I had in mind above afterwards. Feel free to share your thoughts on that whenever you can.
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.
All seems reasonable, and I see you've already started the renaming in #128.
+1 for canApplyDelayFn
, if it's straightforward to implement.
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.
Cool. #128 was just me testing something against Travis, but the commits for this are already in master. I also added the ability to fallback to static or backoff delays if the DelayFunction returns a negative value. Supporting both seemed more consistent.
This PR is in response to #110. It adds a delay function property to RetryPolicy that is used, if set, to compute the next delay from the previous result or exception.
There are some awkward bits here:
net.jodah.failsafe.util.Duration
in signature of the delay function, thoughI really wanted to use
java.time.Duration
.I've done nothing to make them mutually exclusive.
requested delay.