Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Co-authored-by: Christoph Emig <christoph.emig@1und1.de>
  • Loading branch information
Chr3is and c3mig committed May 20, 2022
1 parent 40f0118 commit 6a9f482
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Predicate;

public final class ClassUtils {
Expand Down Expand Up @@ -58,6 +59,19 @@ public static <T> Predicate<T> instantiatePredicateClass(Class<? extends Predica
}
}

public static <T> BiConsumer<Integer, T> instantiateBiConsumer(Class<? extends BiConsumer<Integer, T>> clazz) {
try {
Constructor<? extends BiConsumer<Integer, T>> c = clazz.getConstructor();
if (c != null) {
return c.newInstance();
} else {
throw new InstantiationException(INSTANTIATION_ERROR_PREFIX + clazz.getName());
}
} catch (Exception e) {
throw new InstantiationException(INSTANTIATION_ERROR_PREFIX + clazz.getName(), e);
}
}

public static <T> T instantiateClassDefConstructor(Class<T> clazz) {
//if constructor present then it should have a no arg constructor
//if not present then default constructor is already their
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.junit.Test;

import java.util.function.BiConsumer;
import java.util.function.Predicate;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -22,6 +23,19 @@ public void shouldFailToInstantiatePredicateClass() {
.hasCauseInstanceOf(NoSuchMethodException.class);
}

@Test
public void shouldInstantiateBiConsumerClass(){
assertThat(ClassUtils.instantiateBiConsumer(PublicBiConsumer.class)).isNotNull();
}

@Test
public void shouldFailToInstantiateBiConsumerClassWithoutDefaultConstructor(){
assertThatThrownBy(
() -> ClassUtils.instantiateBiConsumer(NoDefaultConstructorBiConsumer.class))
.isInstanceOf(InstantiationException.class)
.hasCauseInstanceOf(NoSuchMethodException.class);
}

@Test
public void shouldInstantiateClassWithDefaultConstructor() {
assertThat(ClassUtils.instantiateClassDefConstructor(DefaultConstructor.class)).isNotNull();
Expand Down Expand Up @@ -57,6 +71,20 @@ public boolean test(String o) {
}
}

public static class PublicBiConsumer implements BiConsumer<Integer, String>{

@Override
public void accept(Integer integer, String s) {

}
}

public static class NoDefaultConstructorBiConsumer extends PublicBiConsumer {

public NoDefaultConstructorBiConsumer(String foo) {
}
}

public static class NoDefaultConstructor {
public NoDefaultConstructor(String a){}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@
import io.github.resilience4j.common.CommonProperties;
import io.github.resilience4j.common.CompositeCustomizer;
import io.github.resilience4j.common.utils.ConfigUtils;
import io.github.resilience4j.core.ClassUtils;
import io.github.resilience4j.core.ConfigurationNotFoundException;
import io.github.resilience4j.core.IntervalFunction;
import io.github.resilience4j.core.IntervalBiFunction;
import io.github.resilience4j.core.StringUtils;
import io.github.resilience4j.core.*;
import io.github.resilience4j.core.lang.Nullable;
import io.github.resilience4j.retry.RetryConfig;

Expand All @@ -31,6 +27,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Predicate;

/**
Expand Down Expand Up @@ -165,6 +162,12 @@ private RetryConfig buildRetryConfig(RetryConfig.Builder builder,
.instantiatePredicateClass(properties.getResultPredicate());
builder.retryOnResult(predicate);
}

if(properties.getConsumeResultBeforeRetryAttempt() != null){
BiConsumer<Integer, Object> biConsumer = ClassUtils.instantiateBiConsumer(properties.getConsumeResultBeforeRetryAttempt());
builder.consumeResultBeforeRetryAttempt(biConsumer);
}

if (properties.getIntervalBiFunction() != null) {
IntervalBiFunction<Object> intervalBiFunction = ClassUtils
.instantiateIntervalBiFunctionClass(properties.getIntervalBiFunction());
Expand Down Expand Up @@ -288,6 +291,12 @@ public static class InstanceProperties {
@Nullable
private Class<? extends Predicate<Object>> resultPredicate;

/**
* class to be used to perform post actions on the object if it needs to be retried
*/
@Nullable
private Class<? extends BiConsumer<Integer, Object>> consumeResultBeforeRetryAttempt;

/**
* list of retry exception classes
*/
Expand Down Expand Up @@ -405,6 +414,16 @@ public InstanceProperties setResultPredicate(
return this;
}

@Nullable
Class<? extends BiConsumer<Integer, Object>> getConsumeResultBeforeRetryAttempt(){
return consumeResultBeforeRetryAttempt;
}

public InstanceProperties setConsumeResultBeforeRetryAttempt(Class<? extends BiConsumer<Integer, Object>> consumer){
this.consumeResultBeforeRetryAttempt = consumer;
return this;
}

@Nullable
public Class<? extends Throwable>[] getRetryExceptions() {
return retryExceptions;
Expand Down Expand Up @@ -519,7 +538,6 @@ public InstanceProperties setBaseConfig(String baseConfig) {
this.baseConfig = baseConfig;
return this;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@
import io.github.resilience4j.common.CompositeCustomizer;
import io.github.resilience4j.common.RecordFailurePredicate;
import io.github.resilience4j.common.TestIntervalBiFunction;
import io.github.resilience4j.common.utils.ConsumeResultBeforeRetryAttempt;
import io.github.resilience4j.core.ConfigurationNotFoundException;
import io.github.resilience4j.retry.RetryConfig;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

@RunWith(MockitoJUnitRunner.class)
public class RetryConfigurationPropertiesTest {

@Test
@Test
@SuppressWarnings("unchecked")
public void testRetryProperties() {
CommonRetryConfigurationProperties.InstanceProperties instanceProperties1 = new CommonRetryConfigurationProperties.InstanceProperties();
Expand All @@ -47,6 +51,7 @@ public void testRetryProperties() {
instanceProperties1.setEventConsumerBufferSize(100);
instanceProperties1.setRetryExceptions(new Class[]{IllegalStateException.class});
instanceProperties1.setIgnoreExceptions(new Class[]{IllegalArgumentException.class});
instanceProperties1.setConsumeResultBeforeRetryAttempt(ConsumeResultBeforeRetryAttempt.class);
instanceProperties1.setRetryExceptionPredicate(RecordFailurePredicate.class);
instanceProperties1.setFailAfterMaxAttempts(true);

Expand Down Expand Up @@ -77,11 +82,13 @@ public void testRetryProperties() {
assertThat(retry1).isNotNull();
assertThat(retry1.isFailAfterMaxAttempts()).isTrue();
assertThat(retry1.getMaxAttempts()).isEqualTo(3);
assertThat(retry1.getConsumeResultBeforeRetryAttempt().getClass()).isEqualTo(ConsumeResultBeforeRetryAttempt.class);
assertThat(retry2).isNotNull();
assertThat(retry2.getMaxAttempts()).isEqualTo(2);
assertThat(retry2.getIntervalFunction().apply(1)).isEqualTo(99L);
assertThat(retry2.getIntervalFunction().apply(2)).isEqualTo(99L);
assertThat(retry2.isFailAfterMaxAttempts()).isFalse();
assertThat(retry2.getConsumeResultBeforeRetryAttempt()).isNull();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.github.resilience4j.common.utils;

import java.util.function.BiConsumer;

public class ConsumeResultBeforeRetryAttempt implements BiConsumer<Integer, Object> {
@Override
public void accept(Integer integer, Object o) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import io.github.resilience4j.retry.Retry
import org.assertj.core.api.Assertions
import org.junit.Test
import java.time.Duration
import java.util.concurrent.atomic.AtomicInteger
import java.util.function.BiConsumer
import java.util.function.Predicate

class RetryTest {

Expand Down Expand Up @@ -146,4 +149,32 @@ class RetryTest {
// Then the helloWorldService should be invoked 1 time
Assertions.assertThat(helloWorldService.invocationCounter).isEqualTo(1)
}

@Test
fun `should perform consumeResultBeforeRetryAttempt on retry`() {
val helloWorldService = HelloWorldService()
val helloWorldServiceReturnValue = "Hello world"

val shouldRetry = Predicate { s: String? -> helloWorldServiceReturnValue == s }

val consumerInvocations = AtomicInteger(0)
val consumeResultBeforeRetryAttempt = BiConsumer { currentAttempt: Int?, value: String ->
if (helloWorldServiceReturnValue == value) {
consumerInvocations.set(currentAttempt!!)
}
}

val config = io.github.resilience4j.retry.RetryConfig.Builder<String>()
.retryOnResult(shouldRetry)
.consumeResultBeforeRetryAttempt(consumeResultBeforeRetryAttempt)
.build()

val retry = Retry.of("id", config)
val supplier = Retry.decorateSupplier(retry) { helloWorldService.returnHelloWorld() }
val result = supplier.get()

Assertions.assertThat(helloWorldService.invocationCounter).isEqualTo(3)
Assertions.assertThat(result).isEqualTo(helloWorldServiceReturnValue)
Assertions.assertThat(consumerInvocations.get()).isEqualTo(2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.io.Serializable;
import java.time.Duration;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;

Expand All @@ -40,6 +41,7 @@ public class RetryConfig implements Serializable {
private static final IntervalBiFunction DEFAULT_INTERVAL_BI_FUNCTION = IntervalBiFunction.ofIntervalFunction(DEFAULT_INTERVAL_FUNCTION);
private static final Predicate<Throwable> DEFAULT_RECORD_FAILURE_PREDICATE = throwable -> true;


@SuppressWarnings("unchecked")
private Class<? extends Throwable>[] retryExceptions = new Class[0];
@SuppressWarnings("unchecked")
Expand All @@ -51,6 +53,9 @@ public class RetryConfig implements Serializable {
@Nullable
private transient Predicate retryOnResultPredicate;

@Nullable
private BiConsumer consumeResultBeforeRetryAttempt;

private int maxAttempts = DEFAULT_MAX_ATTEMPTS;
private boolean failAfterMaxAttempts = false;
private boolean writableStackTraceEnabled = true;
Expand Down Expand Up @@ -147,6 +152,16 @@ public <T> Predicate<T> getResultPredicate() {
return retryOnResultPredicate;
}

/**
* Return the BiConsumer which performs post actions if the result should be retried.
*
* @param <T> The type of result.
* @return the onFailureBiConsumer
*/
public <T> BiConsumer<Integer, T> getConsumeResultBeforeRetryAttempt(){
return consumeResultBeforeRetryAttempt;
}

public static class Builder<T> {

private int maxAttempts = DEFAULT_MAX_ATTEMPTS;
Expand All @@ -164,6 +179,9 @@ public static class Builder<T> {
@Nullable
private IntervalBiFunction<T> intervalBiFunction;

@Nullable
BiConsumer<Integer, T> consumeResultBeforeRetryAttempt;

@SuppressWarnings("unchecked")
private Class<? extends Throwable>[] retryExceptions = new Class[0];
@SuppressWarnings("unchecked")
Expand All @@ -186,6 +204,8 @@ public String toString() {
retryConfig.append(retryOnResultPredicate);
retryConfig.append(", intervalBiFunction=");
retryConfig.append(intervalBiFunction);
retryConfig.append(", consumeResultBeforeRetryAttempt=");
retryConfig.append(consumeResultBeforeRetryAttempt);
retryConfig.append(", retryExceptions=");
retryConfig.append(retryExceptions);
retryConfig.append(", ignoreExceptions=");
Expand All @@ -201,6 +221,7 @@ public Builder(RetryConfig baseConfig) {
this.maxAttempts = baseConfig.maxAttempts;
this.retryOnExceptionPredicate = baseConfig.retryOnExceptionPredicate;
this.retryOnResultPredicate = baseConfig.retryOnResultPredicate;
this.consumeResultBeforeRetryAttempt = baseConfig.consumeResultBeforeRetryAttempt;
this.failAfterMaxAttempts = baseConfig.failAfterMaxAttempts;
this.writableStackTraceEnabled = baseConfig.writableStackTraceEnabled;
this.retryExceptions = baseConfig.retryExceptions;
Expand Down Expand Up @@ -243,6 +264,17 @@ public Builder<T> retryOnResult(Predicate<T> predicate) {
return this;
}

/**
* Configure a BiConsumer which performs post actions if a result should be retried.
*
* @param consumeResultBeforeRetryAttempt the BiConsumer which performs post actions if a result should be retried.
* @return the RetryConfig.Builder
*/
public Builder<T> consumeResultBeforeRetryAttempt(BiConsumer<Integer, T> consumeResultBeforeRetryAttempt){
this.consumeResultBeforeRetryAttempt = consumeResultBeforeRetryAttempt;
return this;
}

/**
* Configures the Retry to throw a {@link MaxRetriesExceeded} exception once {@link #maxAttempts} has been reached,
* and the result is still not satisfactory (according to {@link #retryOnResultPredicate})
Expand Down Expand Up @@ -366,6 +398,7 @@ public RetryConfig build() {
config.writableStackTraceEnabled = writableStackTraceEnabled;
config.retryOnExceptionPredicate = retryOnExceptionPredicate;
config.retryOnResultPredicate = retryOnResultPredicate;
config.consumeResultBeforeRetryAttempt = consumeResultBeforeRetryAttempt;
config.retryExceptions = retryExceptions;
config.ignoreExceptions = ignoreExceptions;
config.exceptionPredicate = createExceptionPredicate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;

Expand All @@ -49,6 +50,10 @@ public class RetryImpl<T> implements Retry {
private final RetryEventProcessor eventProcessor;
@Nullable
private final Predicate<T> resultPredicate;

@Nullable
private final BiConsumer<Integer, T> consumeResultBeforeRetryAttempt;

private final String name;
private final RetryConfig config;
private final Map<String, String> tags;
Expand All @@ -75,6 +80,7 @@ public RetryImpl(String name, RetryConfig config, Map<String, String> tags) {
this.intervalBiFunction = config.getIntervalBiFunction();
this.exceptionPredicate = config.getExceptionPredicate();
this.resultPredicate = config.getResultPredicate();
this.consumeResultBeforeRetryAttempt = config.getConsumeResultBeforeRetryAttempt();
this.metrics = this.new RetryMetrics();
this.eventProcessor = new RetryEventProcessor();
succeededAfterRetryCounter = new LongAdder();
Expand Down Expand Up @@ -180,6 +186,9 @@ public boolean onResult(T result) {
if (currentNumOfAttempts >= maxAttempts) {
return false;
} else {
if(consumeResultBeforeRetryAttempt != null){
consumeResultBeforeRetryAttempt.accept(currentNumOfAttempts, result);
}
waitIntervalAfterFailure(currentNumOfAttempts, Either.right(result));
return true;
}
Expand Down Expand Up @@ -327,6 +336,9 @@ public long onResult(T result) {
if (null != resultPredicate && resultPredicate.test(result)) {
int attempt = numOfAttempts.incrementAndGet();
if (attempt >= maxAttempts) {
if(consumeResultBeforeRetryAttempt != null){
consumeResultBeforeRetryAttempt.accept(attempt, result);
}
return -1;
}
return intervalBiFunction.apply(attempt, Either.right(result));
Expand Down
Loading

0 comments on commit 6a9f482

Please sign in to comment.