Skip to content

GH-184: Expression Evaluation at Runtime #298

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

Merged
merged 4 commits into from
May 4, 2022

Conversation

garyrussell
Copy link
Contributor

Resolves #184

Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

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

I'm not fully buying this evaluation mode and would live with just a runtime one like we do in many other places. Plus it might turn in some use-case that end-user would like to have a mix of modes for different expressions. Also it looks a bit clumsy for nested annotations...

@@ -218,7 +240,7 @@ private boolean retryForException(Throwable ex) {

@Override
public String toString() {
return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + this.maxAttempts + "]";
return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + this.maxAttemptsSupplier + "]";
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + this.maxAttemptsSupplier + "]";
return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + getMaxAttempts() + "]";

?

Copy link
Member

Choose a reason for hiding this comment

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

Looks like has not been addressed.

: random.nextInt((int) (maxBackOffPeriod - minBackOffPeriod));
sleeper.sleep(minBackOffPeriod + delta);
Long min = this.minBackOffPeriod.get();
long delta = this.maxBackOffPeriod == this.minBackOffPeriod ? 0
Copy link
Member

Choose a reason for hiding this comment

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

I think this is something wrong: you compare Supplier instances - not their values.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops.

@garyrussell
Copy link
Contributor Author

garyrussell commented May 3, 2022

I don't think we should add the overhead of runtime evaluation for all users; when most won't need it; it also causes problems because the SimpleRetryPolicy must be serializable (used in the circuit breaker retry context. See the note in the README and javadocs.

If you have a better suggestion for the toggle, I am open to hearing it.

@garyrussell
Copy link
Contributor Author

One alternative I considered was template expressions #{...} are evaluated at initialization and non-template expressions are evaluated at runtime.

This would be a behavior change, but it's a major release, so I could agree to it.

@artembilan
Copy link
Member

Yeah...

If we would like to be consistent with an XML configuration and its SpEL support, then indeed that @Retryable(maxAttempts) has to be a string and support a "templated" expression evaluation during configuration phase.
Meanwhile a maxAttemptsExpression would really lead to the runtime expression evaluation.
Just similar to what we have so far in Spring Integration.

Looking to the same @Retryable right now we have:

	int maxAttempts() default 3;

	String maxAttemptsExpression() default "";

	String exceptionExpression() default "";

Where it is not clear that maxAttemptsExpression is going to be evaluated at configuration phase or at runtime, but exceptionExpression states it clearly that it is for runtime.
That's another argument from me against a common evaluation mode option for the whole annotation.

So, if we cannot change simple value attributes (like that maxAttempts to String), then your suggestion for templating on companion SpEL attributes doesn't sound bad to distinguish runtime and configuration phase evaluations.

@garyrussell
Copy link
Contributor Author

I think changing the types would be too much of a breaking change - even for a major release (I suspect many users don't even use expressions); I'll go with templating idea.

@@ -218,7 +240,7 @@ private boolean retryForException(Throwable ex) {

@Override
public String toString() {
return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + this.maxAttempts + "]";
return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + this.maxAttemptsSupplier + "]";
Copy link
Member

Choose a reason for hiding this comment

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

Looks like has not been addressed.

With earlier versions, evaluation was always performed during initialization (except for `Retryable.exceptionExpression` which is always evaluated at runtime).
When evaluating at runtime, a root object containing the method arguments is passed to the evaluation context.

**Note:** The arguments are not available until the method has been called at least once; they will be null initially, which means, for example, you can't set the initial `maxAttempts` using an argument value, you can, however, change the `maxAttempts` after the first failure and before any retries are performed.
Copy link
Member

Choose a reason for hiding this comment

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

Is it only a limitation for the maxAttempts option?

I see open() is done far before a doWithRetry(), so perhaps those args does not make sense for circuit breaker...

Looks like BackOff expressions don't suffer from this limitation: it is indeed called in the catch from the doWithRetry().

Do I miss anything?

Thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right - the back off is not evaluated until after the method is called so, yes, the limitation is only on the maxAttempts.

Good point about the CB.

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 turns out that we can't use args at all for any stateful retry (including circuit breakers) because you would get the args from the previous call (which may be different).

}
if (args == null) {
args = Args.NO_ARGS;
}
}
return expression.getValue(this.evaluationContext, args, type);
Copy link
Member

Choose a reason for hiding this comment

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

Is it expected to propagate an args var as null for statefull unlike for stateless with an Args.NO_ARGS?

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; I want them to get a hard error EL1007E: Property or field 'args' cannot be found on null.

I failed to save the doc change for this; pushing now...

@artembilan artembilan merged commit 47c1e52 into spring-projects:main May 4, 2022
@garyrussell garyrussell deleted the GH-184 branch May 4, 2022 19:28
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.

Annotation Improvements
2 participants