|
|
|
@ -17,7 +17,9 @@ |
|
|
|
package org.springframework.core.retry; |
|
|
|
package org.springframework.core.retry; |
|
|
|
|
|
|
|
|
|
|
|
import java.time.Duration; |
|
|
|
import java.time.Duration; |
|
|
|
|
|
|
|
import java.util.concurrent.atomic.AtomicInteger; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import org.junit.jupiter.api.BeforeEach; |
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
|
|
|
|
|
|
|
|
import org.springframework.util.backoff.FixedBackOff; |
|
|
|
import org.springframework.util.backoff.FixedBackOff; |
|
|
|
@ -36,95 +38,99 @@ class RetryTemplateTests { |
|
|
|
|
|
|
|
|
|
|
|
private final RetryTemplate retryTemplate = new RetryTemplate(); |
|
|
|
private final RetryTemplate retryTemplate = new RetryTemplate(); |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
void retryWithSuccess() throws Exception { |
|
|
|
|
|
|
|
Retryable<String> retryable = new Retryable<>() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int failure; |
|
|
|
@BeforeEach |
|
|
|
|
|
|
|
void configureTemplate() { |
|
|
|
|
|
|
|
this.retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Test |
|
|
|
public String execute() throws Exception { |
|
|
|
void retryWithImmediateSuccess() throws Exception { |
|
|
|
if (failure++ < 2) { |
|
|
|
AtomicInteger invocationCount = new AtomicInteger(); |
|
|
|
throw new Exception("Error while invoking greeting service"); |
|
|
|
Retryable<String> retryable = () -> { |
|
|
|
} |
|
|
|
invocationCount.incrementAndGet(); |
|
|
|
return "hello world"; |
|
|
|
return "always succeeds"; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
assertThat(invocationCount).hasValue(0); |
|
|
|
public String getName() { |
|
|
|
assertThat(retryTemplate.execute(retryable)).isEqualTo("always succeeds"); |
|
|
|
return "greeting service"; |
|
|
|
assertThat(invocationCount).hasValue(1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
void retryWithSuccessAfterInitialFailures() throws Exception { |
|
|
|
|
|
|
|
AtomicInteger invocationCount = new AtomicInteger(); |
|
|
|
|
|
|
|
Retryable<String> retryable = () -> { |
|
|
|
|
|
|
|
if (invocationCount.incrementAndGet() <= 2) { |
|
|
|
|
|
|
|
throw new Exception("Boom!"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return "finally succeeded"; |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10))); |
|
|
|
assertThat(invocationCount).hasValue(0); |
|
|
|
|
|
|
|
assertThat(retryTemplate.execute(retryable)).isEqualTo("finally succeeded"); |
|
|
|
assertThat(retryTemplate.execute(retryable)).isEqualTo("hello world"); |
|
|
|
assertThat(invocationCount).hasValue(3); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
void retryWithFailure() { |
|
|
|
void retryWithExhaustedPolicy() { |
|
|
|
Exception exception = new Exception("Error while invoking greeting service"); |
|
|
|
AtomicInteger invocationCount = new AtomicInteger(); |
|
|
|
|
|
|
|
RuntimeException exception = new RuntimeException("Boom!"); |
|
|
|
|
|
|
|
|
|
|
|
Retryable<String> retryable = new Retryable<>() { |
|
|
|
Retryable<String> retryable = new Retryable<>() { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public String execute() throws Exception { |
|
|
|
public String execute() { |
|
|
|
|
|
|
|
invocationCount.incrementAndGet(); |
|
|
|
throw exception; |
|
|
|
throw exception; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public String getName() { |
|
|
|
public String getName() { |
|
|
|
return "greeting service"; |
|
|
|
return "test"; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10))); |
|
|
|
assertThat(invocationCount).hasValue(0); |
|
|
|
|
|
|
|
|
|
|
|
assertThatExceptionOfType(RetryException.class) |
|
|
|
assertThatExceptionOfType(RetryException.class) |
|
|
|
.isThrownBy(() -> retryTemplate.execute(retryable)) |
|
|
|
.isThrownBy(() -> retryTemplate.execute(retryable)) |
|
|
|
.withMessage("Retry policy for operation 'greeting service' exhausted; aborting execution") |
|
|
|
.withMessage("Retry policy for operation 'test' exhausted; aborting execution") |
|
|
|
.withCause(exception); |
|
|
|
.withCause(exception); |
|
|
|
|
|
|
|
// 4 = 1 initial invocation + 3 retry attempts
|
|
|
|
|
|
|
|
assertThat(invocationCount).hasValue(4); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
void retrySpecificException() { |
|
|
|
void retryWithFailingRetryableAndCustomRetryPolicy() { |
|
|
|
|
|
|
|
AtomicInteger invocationCount = new AtomicInteger(); |
|
|
|
@SuppressWarnings("serial") |
|
|
|
RuntimeException exception = new NumberFormatException(); |
|
|
|
class TechnicalException extends Exception { |
|
|
|
|
|
|
|
public TechnicalException(String message) { |
|
|
|
|
|
|
|
super(message); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TechnicalException technicalException = new TechnicalException("Error while invoking greeting service"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Retryable<String> retryable = new Retryable<>() { |
|
|
|
Retryable<String> retryable = new Retryable<>() { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public String execute() throws TechnicalException { |
|
|
|
public String execute() { |
|
|
|
throw technicalException; |
|
|
|
invocationCount.incrementAndGet(); |
|
|
|
|
|
|
|
throw exception; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public String getName() { |
|
|
|
public String getName() { |
|
|
|
return "greeting service"; |
|
|
|
return "always fails"; |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RetryPolicy retryPolicy = () -> new RetryExecution() { |
|
|
|
|
|
|
|
int retryAttempts; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public boolean shouldRetry(Throwable throwable) { |
|
|
|
|
|
|
|
return (this.retryAttempts++ < 3 && throwable instanceof TechnicalException); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AtomicInteger retryCount = new AtomicInteger(); |
|
|
|
|
|
|
|
// Custom RetryPolicy that only retries for a NumberFormatException and max 5 retry attempts.
|
|
|
|
|
|
|
|
RetryPolicy retryPolicy = () -> throwable -> (retryCount.incrementAndGet() <= 5 && throwable instanceof NumberFormatException); |
|
|
|
retryTemplate.setRetryPolicy(retryPolicy); |
|
|
|
retryTemplate.setRetryPolicy(retryPolicy); |
|
|
|
retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10))); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assertThat(invocationCount).hasValue(0); |
|
|
|
|
|
|
|
assertThat(retryCount).hasValue(0); |
|
|
|
assertThatExceptionOfType(RetryException.class) |
|
|
|
assertThatExceptionOfType(RetryException.class) |
|
|
|
.isThrownBy(() -> retryTemplate.execute(retryable)) |
|
|
|
.isThrownBy(() -> retryTemplate.execute(retryable)) |
|
|
|
.withMessage("Retry policy for operation 'greeting service' exhausted; aborting execution") |
|
|
|
.withMessage("Retry policy for operation 'always fails' exhausted; aborting execution") |
|
|
|
.withCause(technicalException); |
|
|
|
.withCause(exception); |
|
|
|
|
|
|
|
// 6 = 1 initial invocation + 5 retry attempts
|
|
|
|
|
|
|
|
assertThat(invocationCount).hasValue(6); |
|
|
|
|
|
|
|
assertThat(retryCount).hasValue(6); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|