Skip to content

Add rethrow flag #265

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

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -220,13 +220,16 @@ private MethodInterceptor getStatelessInterceptor(Object target, Method method,
RetryTemplate template = createTemplate(retryable.listeners());
template.setRetryPolicy(getRetryPolicy(retryable));
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
template.setNoRecoveryForNotRetryable(retryable.rethrow());
return RetryInterceptorBuilder.stateless().retryOperations(template).label(retryable.label())
.recoverer(getRecoverer(target, method)).build();
.recoverer(getRecoverer(target, method, retryable.rethrow())).build();
}

private MethodInterceptor getStatefulInterceptor(Object target, Method method, Retryable retryable) {
boolean rethrow = retryable.rethrow();
RetryTemplate template = createTemplate(retryable.listeners());
template.setRetryContextCache(this.retryContextCache);
template.setNoRecoveryForNotRetryable(rethrow);

CircuitBreaker circuit = AnnotatedElementUtils.findMergedAnnotation(method, CircuitBreaker.class);
if (circuit == null) {
Expand All @@ -244,15 +247,15 @@ private MethodInterceptor getStatefulInterceptor(Object target, Method method, R
label = method.toGenericString();
}
return RetryInterceptorBuilder.circuitBreaker().keyGenerator(new FixedKeyGenerator("circuit"))
.retryOperations(template).recoverer(getRecoverer(target, method)).label(label).build();
.retryOperations(template).recoverer(getRecoverer(target, method, rethrow)).label(label).build();
}
RetryPolicy policy = getRetryPolicy(retryable);
template.setRetryPolicy(policy);
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
String label = retryable.label();
return RetryInterceptorBuilder.stateful().keyGenerator(this.methodArgumentsKeyGenerator)
.newMethodArgumentsIdentifier(this.newMethodArgumentsIdentifier).retryOperations(template).label(label)
.recoverer(getRecoverer(target, method)).build();
.recoverer(getRecoverer(target, method, rethrow)).build();
}

private long getOpenTimeout(CircuitBreaker circuit) {
Expand Down Expand Up @@ -296,7 +299,7 @@ private RetryListener[] getListenersBeans(String[] listenersBeanNames) {
return listeners;
}

private MethodInvocationRecoverer<?> getRecoverer(Object target, Method method) {
private MethodInvocationRecoverer<?> getRecoverer(Object target, Method method, boolean rethrow) {
if (target instanceof MethodInvocationRecoverer) {
return (MethodInvocationRecoverer<?>) target;
}
Expand All @@ -313,7 +316,9 @@ public void doWith(Method method) throws IllegalArgumentException, IllegalAccess
if (!foundRecoverable.get()) {
return null;
}
return new RecoverAnnotationRecoveryHandler<Object>(target, method);
RecoverAnnotationRecoveryHandler recoveryHandler = new RecoverAnnotationRecoveryHandler<Object>(target, method);
recoveryHandler.setThrowLastExceptionWhenNoRecoverMethod(rethrow);
return recoveryHandler;
}

private RetryPolicy getRetryPolicy(Annotation retryable) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -62,16 +62,23 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco

private String recoverMethodName;

private boolean throwLastExceptionWhenNoRecoverMethod;

public RecoverAnnotationRecoveryHandler(Object target, Method method) {
this.target = target;
init(target, method);
}

public void setThrowLastExceptionWhenNoRecoverMethod(boolean throwLastExceptionWhenNoRecoverMethod) {
this.throwLastExceptionWhenNoRecoverMethod = throwLastExceptionWhenNoRecoverMethod;
}

@Override
public T recover(Object[] args, Throwable cause) {
Method method = findClosestMatch(args, cause.getClass());
if (method == null) {
throw new ExhaustedRetryException("Cannot locate recovery method", cause);
throw throwLastExceptionWhenNoRecoverMethod && cause instanceof RuntimeException ? (RuntimeException) cause
: new ExhaustedRetryException("Cannot locate recovery method", cause);
}
SimpleMetadata meta = this.methods.get(method);
Object[] argsToUse = meta.getArgs(cause, args);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -130,4 +130,12 @@
*/
String[] listeners() default {};

/**
* When true raw exceptions are thrown without being wrapped and no recovery is
* performed for not-retryable exceptions.
* @return true if to rethrow raw exceptions, default false
* @since 1.3.3
*/
boolean rethrow() default false;

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2006-2019 the original author or authors.
* Copyright 2006-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -199,23 +199,24 @@ public RetryContext open(RetryContext parent) {
return new SimpleRetryContext(parent);
}

private static class SimpleRetryContext extends RetryContextSupport {

public SimpleRetryContext(RetryContext parent) {
super(parent);
}

}

/**
* Delegates to an exception classifier.
* @param ex
* @return true if this exception or its ancestors have been registered as retryable.
Copy link
Contributor

Choose a reason for hiding this comment

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

Need @since 1.3.3 now that it is public.

* @since 1.3.3
*/
private boolean retryForException(Throwable ex) {
public boolean retryForException(Throwable ex) {
return this.retryableClassifier.classify(ex);
}

private static class SimpleRetryContext extends RetryContextSupport {

public SimpleRetryContext(RetryContext parent) {
super(parent);
}

}

@Override
public String toString() {
return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + this.maxAttempts + "]";
Expand Down
29 changes: 19 additions & 10 deletions src/main/java/org/springframework/retry/support/RetryTemplate.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2006-2020 the original author or authors.
* Copyright 2006-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -96,6 +96,8 @@ public class RetryTemplate implements RetryOperations {

private boolean throwLastExceptionOnExhausted;

private boolean noRecoveryForNotRetryable;

/**
* Main entry point to configure RetryTemplate using fluent API. See
* {@link RetryTemplateBuilder} for usage examples and details.
Expand Down Expand Up @@ -124,6 +126,14 @@ public void setThrowLastExceptionOnExhausted(boolean throwLastExceptionOnExhaust
this.throwLastExceptionOnExhausted = throwLastExceptionOnExhausted;
}

/**
* @param noRecoveryForNotRetryable the noRecoveryForNotRetryable to set
* @since 1.3.3
*/
public void setNoRecoveryForNotRetryable(boolean noRecoveryForNotRetryable) {
this.noRecoveryForNotRetryable = noRecoveryForNotRetryable;
}

/**
* Public setter for the {@link RetryContextCache}.
* @param retryContextCache the {@link RetryContextCache} to set.
Expand Down Expand Up @@ -366,7 +376,6 @@ protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback
}
throw RetryTemplate.<E>wrapIfNecessary(e);
}

}

/*
Expand All @@ -384,7 +393,7 @@ protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback
}

exhausted = true;
return handleRetryExhausted(recoveryCallback, context, state);
return handleRetryExhausted(recoveryCallback, context, state, retryPolicy);

}
catch (Throwable e) {
Expand Down Expand Up @@ -462,7 +471,6 @@ private void registerContext(RetryContext context, RetryState state) {
* was encountered
*/
protected RetryContext open(RetryPolicy retryPolicy, RetryState state) {

if (state == null) {
return doOpenInternal(retryPolicy);
}
Expand Down Expand Up @@ -496,7 +504,6 @@ protected RetryContext open(RetryPolicy retryPolicy, RetryState state) {
context.removeAttribute(RetryContext.EXHAUSTED);
context.removeAttribute(RetryContext.RECOVERED);
return context;

}

private RetryContext doOpenInternal(RetryPolicy retryPolicy, RetryState state) {
Expand Down Expand Up @@ -529,12 +536,16 @@ private RetryContext doOpenInternal(RetryPolicy retryPolicy) {
* @return T the payload to return
* @throws Throwable if there is an error
*/
protected <T> T handleRetryExhausted(RecoveryCallback<T> recoveryCallback, RetryContext context, RetryState state)
throws Throwable {
protected <T> T handleRetryExhausted(RecoveryCallback<T> recoveryCallback, RetryContext context, RetryState state,
RetryPolicy retryPolicy) throws Throwable {
context.setAttribute(RetryContext.EXHAUSTED, true);
if (state != null && !context.hasAttribute(GLOBAL_STATE)) {
this.retryContextCache.remove(state.getKey());
}
if (this.noRecoveryForNotRetryable && retryPolicy instanceof SimpleRetryPolicy
&& !((SimpleRetryPolicy) retryPolicy).retryForException(context.getLastThrowable())) {
throw context.getLastThrowable();
}
if (recoveryCallback != null) {
T recovered = recoveryCallback.recover(context);
context.setAttribute(RetryContext.RECOVERED, true);
Expand All @@ -548,7 +559,7 @@ protected <T> T handleRetryExhausted(RecoveryCallback<T> recoveryCallback, Retry
}

protected <E extends Throwable> void rethrow(RetryContext context, String message) throws E {
if (this.throwLastExceptionOnExhausted) {
if (this.throwLastExceptionOnExhausted || this.noRecoveryForNotRetryable) {
@SuppressWarnings("unchecked")
E rethrow = (E) context.getLastThrowable();
throw rethrow;
Expand All @@ -572,15 +583,13 @@ protected boolean shouldRethrow(RetryPolicy retryPolicy, RetryContext context, R
}

private <T, E extends Throwable> boolean doOpenInterceptors(RetryCallback<T, E> callback, RetryContext context) {

boolean result = true;

for (RetryListener listener : this.listeners) {
result = result && listener.open(context, callback);
}

return result;

}

private <T, E extends Throwable> void doCloseInterceptors(RetryCallback<T, E> callback, RetryContext context,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -255,6 +255,22 @@ public void testExpression() throws Exception {
context.close();
}

@Test
public void rethrow() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
RethrowService service = context.getBean(RethrowService.class);
for (int i = 0; i < 3; i++) {
try {
service.service();
}
catch (RuntimeException e) {
assertEquals("Planned", e.getMessage());
}
}
assertEquals(3, service.getCount());
context.close();
}

private Object target(Object target) {
if (!AopUtils.isAopProxy(target)) {
return target;
Expand Down Expand Up @@ -434,6 +450,11 @@ public ExcludesOnlyService excludesOnly() {
return new ExcludesOnlyService();
}

@Bean
public RethrowService rethrowService() {
return new RethrowService();
}

@Bean
public MethodInterceptor retryInterceptor() {
return RetryInterceptorBuilder.stateless().maxAttempts(5).build();
Expand Down Expand Up @@ -696,6 +717,23 @@ public int getCount() {

}

private static class RethrowService {

private int count = 0;

@Retryable(include = IllegalArgumentException.class, rethrow = true)
public void service() {
if (this.count++ < 2) {
throw new RuntimeException("Planned");
}
}

public int getCount() {
return this.count;
}

}

public static class ExceptionChecker {

public boolean shouldRetry(Throwable t) {
Expand Down
Loading