Capped exponential backoff factories validate the initial interval on first use instead of at construction
Resilience4j version: 2.4.0 and current master (a8a3316)
Java version: 21
Problem
The capped factories validate the maximum interval eagerly but build their uncapped delegate inside the returned lambda, so the initial interval is only validated on first use.
IntervalFunction.ofExponentialBackoff(0, 2.0); // throws IllegalArgumentException
IntervalFunction.ofExponentialBackoff(0, 2.0, 5000); // returns normallyofExponentialRandomBackoff(long, double, double, long) is the same.
Zero is legal elsewhere, since RetryConfig.Builder.waitDuration takes Duration.ZERO as retry immediately, but it cannot mean that here: ofExponentialBackoff(initial, multiplier) is of(initial, x -> x * multiplier), so a zero initial interval stays zero forever.
Configuration reaches it. wait-duration: 0 passes CommonRetryConfigurationProperties.setWaitDuration, which rejects only negatives. With enable-exponential-backoff and a multiplier set, exponential-max-wait-duration decides which factory is used: without it the two-argument overload rejects the value while the registry is built, with it the capped overload accepts it.
RetryImpl calls the interval function outside the try block that rethrows the exception being retried, so the validation error replaces the original failure. Today this fails with IllegalArgumentException: Illegal argument interval: 0 millis is less than 1 rather than the exception it was retrying:
@Test
void exceptionBeingRetriedShouldSurviveTheRetry() {
RetryConfig config = RetryConfig.custom()
.maxAttempts(3)
.intervalFunction(IntervalFunction.ofExponentialBackoff(0, 2.0, 5000))
.build();
Retry retry = Retry.of("example", config);
Supplier<String> supplier = Retry.decorateSupplier(retry, () -> {
throw new IllegalStateException("real failure");
});
assertThatThrownBy(supplier::get)
.isInstanceOf(IllegalStateException.class)
.hasMessage("real failure");
}Compatibility
Checking eagerly is a behaviour change: an application with that configuration starts today, and would fail to start instead. #2108 made the same move for the randomization factor in one of these two factories.
Source: resilience4j/resilience4j