static IntervalFunction ofExponentialBackoff(long initialIntervalMillis) { return ofExponentialBackoff(initialIntervalMillis, DEFAULT_MULTIPLIER); }
static IntervalFunction ofExponentialBackoff() { return ofExponentialBackoff(DEFAULT_INITIAL_INTERVAL, DEFAULT_MULTIPLIER); }
static IntervalFunction ofExponentialBackoff(Duration initialInterval, double multiplier) { return ofExponentialBackoff(initialInterval.toMillis(), multiplier); }
static IntervalFunction ofExponentialBackoff(Duration initialInterval) { return ofExponentialBackoff(initialInterval.toMillis(), DEFAULT_MULTIPLIER); }
@Test public void shouldPassPositiveMultiplier() { // Given final Duration duration = Duration.ofMillis(100); final float greaterThanOneMultiplier = 1.0001f; // When IntervalFunction.ofExponentialBackoff(duration, greaterThanOneMultiplier); IntervalFunction.ofExponentialRandomBackoff(duration, greaterThanOneMultiplier); }
@Test public void generatesExponentialIntervals() { final IntervalFunction f = IntervalFunction.ofExponentialBackoff(100, 1.5); long prevV = f.apply(1); for (int i = 2; i < 50; i++) { //When final long v = f.apply(i); // Then Assertions.assertThat(v).isGreaterThan(prevV); prevV = v; } }
@Test public void shouldRejectOutOfBoundsMultiplier() { // Given final Duration duration = Duration.ofMillis(100); final float lessThenOneMultiplier = 0.9999f; // When final List<Try> tries = List.of( Try.of(() -> IntervalFunction.ofExponentialBackoff(duration, lessThenOneMultiplier)), Try.of(() -> IntervalFunction.ofExponentialRandomBackoff(duration, lessThenOneMultiplier)) ); // Then Assertions.assertThat(tries.forAll(Try::isFailure)).isTrue(); Assertions.assertThat(tries.map(Try::getCause).forAll(t -> t instanceof IllegalArgumentException)).isTrue(); }
@Test public void shouldRejectAttemptLessThenOne() { // Given final List<IntervalFunction> fns = List.of( IntervalFunction.ofDefaults(), IntervalFunction.ofRandomized(), IntervalFunction.ofExponentialBackoff(), IntervalFunction.ofExponentialRandomBackoff() ); // When final List<Try> tries = fns.map(fn -> Try.of(() -> fn.apply(0))); // Then Assertions.assertThat(tries.forAll(Try::isFailure)).isTrue(); Assertions.assertThat(tries.map(Try::getCause).forAll(t -> t instanceof IllegalArgumentException)).isTrue(); }
@Test public void shouldPassAttemptGreaterThenZero() { // Given final List<IntervalFunction> fns = List.of( IntervalFunction.ofDefaults(), IntervalFunction.ofRandomized(), IntervalFunction.ofExponentialBackoff(), IntervalFunction.ofExponentialRandomBackoff() ); // When final List<Try> tries1 = fns.map(fn -> Try.of(() -> fn.apply(1))); final List<Try> tries2 = fns.map(fn -> Try.of(() -> fn.apply(2))); // Then Assertions.assertThat(tries1.forAll(Try::isFailure)).isFalse(); Assertions.assertThat(tries2.forAll(Try::isFailure)).isFalse(); }
@Test public void shouldTakeIntoAccountBackoffFunction() { // Given the HelloWorldService throws an exception BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!")); // Create a Retry with a backoff function doubling the interval RetryConfig config = RetryConfig.custom().intervalFunction(IntervalFunction.ofExponentialBackoff(500, 2.0)) .build(); Retry retry = Retry.of("id", config); // Decorate the invocation of the HelloWorldService CheckedFunction0<String> retryableSupplier = Retry .decorateCheckedSupplier(retry, helloWorldService::returnHelloWorld); // When Try<String> result = Try.of(retryableSupplier); // Then the slept time should be according to the backoff function BDDMockito.then(helloWorldService).should(Mockito.times(3)).returnHelloWorld(); assertThat(sleptTime).isEqualTo( RetryConfig.DEFAULT_WAIT_DURATION + RetryConfig.DEFAULT_WAIT_DURATION * 2); } }