Skip to content
Open
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ Retrier retrier = new Retrier.Builder().withWaitStrategy(Retrier.Strategies.wait
Alternatively, it is possible to use a provided exponential wait strategy which increases the wait between retries:
```java
Retrier retrier = new Retrier.Builder().withWaitStrategy(Retrier.Strategies.waitExponential()).build();
//same as
Retrier retrier = new Retrier.Builder().withWaitStrategy(Retrier.Strategies.waitExponential(1, 2)).build();
//wait 1s, 2s, 4s, ... between retries
Retrier retrier = new Retrier.Builder().withWaitStrategy(Retrier.Strategies.waitExponential(1000, 2)).build();
```

#### resultRetryStrategy
Expand Down
63 changes: 32 additions & 31 deletions src/main/java/io/github/alexo/retrier/Retrier.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@
*
* @author Alex Objelean
*/
public class Retrier {
public class Retrier<T> {

@FunctionalInterface
public interface GiveUpStrategy {
<T> T whenNoMoreAttempts(T lastResult, Exception lastException) throws Exception;
public interface GiveUpStrategy<T> {
T whenNoMoreAttempts(T lastResult, Exception lastException) throws Exception;
}

private static final Retrier SINGLE_ATTEMPT = new Retrier.Builder().withStopStrategy(Strategies.stopAfter(1))
private static final Retrier<Object> SINGLE_ATTEMPT = new Retrier.Builder<Object>().withStopStrategy(Strategies.stopAfter(1))
.build();

/**
* Retrier that executes every operation exactly once. The operation is never retried. All exceptions are propagated
* to the caller.
*/
public static Retrier singleAttempt() {
public static Retrier<Object> singleAttempt() {
return SINGLE_ATTEMPT;
}

Expand All @@ -37,7 +37,7 @@ public static Retrier singleAttempt() {
/**
* Strategies used to check if the retry is required given a successful execution with a result.
*/
private final Predicate<Object> resultRetryStrategy;
private final Predicate<T> resultRetryStrategy;
/**
* Strategies used to check if the retry should be stopped given the provided number of attempts already performed.
* Useful to limit the total number of attempts to a bounded value. By default this value is unbounded.
Expand All @@ -51,21 +51,21 @@ public static Retrier singleAttempt() {
/**
* What to do when all attempts have been exhausted and the retrier still wasn't able to perform the operation.
*/
private final GiveUpStrategy giveUpStrategy;
private final GiveUpStrategy<T> giveUpStrategy;

/**
* Utility class responsible for creating several useful types of wait strategy used by Retrier.
*
* @author Alex Objelean
*/
public static class Strategies {
public static Function<Integer, Long> waitExponential(final double backoffBase) {
public static Function<Integer, Long> waitExponential(final long startWaitMillis, final double backoffBase) {
return attempts -> {
if (attempts > 0) {
final double backoffMillis = Math.pow(backoffBase, attempts);
final double backoffMillis = startWaitMillis * Math.pow(backoffBase, attempts);
return Math.min(1000L, Math.round(backoffMillis));
}
return 0l;
return 0L;
};
}

Expand All @@ -78,7 +78,11 @@ public static Function<Integer, Long> waitConstantly(final long delay) {
}

public static Function<Integer, Long> waitExponential() {
return waitExponential(2);
return waitExponential(2.0D);
}

public static Function<Integer, Long> waitExponential(final double backoffBase) {
return waitExponential(1L, 2.0D);
}

/**
Expand All @@ -99,51 +103,48 @@ public static Predicate<Exception> retryOn(Class<? extends Throwable>... excepti
/**
* Default builder will retry on any exception for unlimited number of times without waiting between executions.
*/
public static class Builder {
public static class Builder<T> {
private Predicate<Exception> failedRetryStrategy = e -> true;
private GiveUpStrategy giveUpStrategy = new GiveUpStrategy() {
@Override
public <T> T whenNoMoreAttempts(final T lastResult, final Exception lastException) throws Exception {
if (lastException != null) {
throw lastException;
} else {
return lastResult;
}
private final GiveUpStrategy<T> giveUpStrategy = (lastResult, lastException) -> {
if (lastException != null) {
throw lastException;
} else {
return lastResult;
}
};

private Predicate<Object> resultRetryStrategy = e -> false;
private Predicate<T> resultRetryStrategy = e -> false;
private Predicate<Integer> stopStrategy = attempt -> false;
private Function<Integer, Long> waitStrategy = attempt -> 0l;
private Function<Integer, Long> waitStrategy = attempt -> 0L;

public Retrier build() {
return new Retrier(failedRetryStrategy, resultRetryStrategy, stopStrategy, waitStrategy, giveUpStrategy);
public Retrier<T> build() {
return new Retrier<T>(failedRetryStrategy, resultRetryStrategy, stopStrategy, waitStrategy, giveUpStrategy);
}

public Builder withFailedRetryStrategy(final Predicate<Exception> failedRetryStrategy) {
public Builder<T> withFailedRetryStrategy(final Predicate<Exception> failedRetryStrategy) {
this.failedRetryStrategy = requireNonNull(failedRetryStrategy);
return this;
}

public Builder withResultRetryStrategy(final Predicate<Object> resultRetryStrategy) {
public Builder<T> withResultRetryStrategy(final Predicate<T> resultRetryStrategy) {
this.resultRetryStrategy = requireNonNull(resultRetryStrategy);
return this;
}

public Builder withStopStrategy(final Predicate<Integer> stopStrategy) {
public Builder<T> withStopStrategy(final Predicate<Integer> stopStrategy) {
this.stopStrategy = requireNonNull(stopStrategy);
return this;
}

public Builder withWaitStrategy(final Function<Integer, Long> waitStrategy) {
public Builder<T> withWaitStrategy(final Function<Integer, Long> waitStrategy) {
this.waitStrategy = requireNonNull(waitStrategy);
return this;
}
}

private Retrier(final Predicate<Exception> exceptionRetryStrategy, final Predicate<Object> resultRetryStrategy,
private Retrier(final Predicate<Exception> exceptionRetryStrategy, final Predicate<T> resultRetryStrategy,
final Predicate<Integer> stopStrategy, final Function<Integer, Long> waitStrategy,
final GiveUpStrategy giveUpStrategy) {
final GiveUpStrategy<T> giveUpStrategy) {
this.exceptionRetryStrategy = exceptionRetryStrategy;
this.resultRetryStrategy = resultRetryStrategy;
this.stopStrategy = stopStrategy;
Expand All @@ -160,7 +161,7 @@ private Retrier(final Predicate<Exception> exceptionRetryStrategy, final Predica
* @return the result of callable invocation.
* @throws Exception if the original callback execution failed and {@link Retrier} has decided to stop retrying.
*/
public <T> T execute(final Callable<T> callable) throws Exception {
public T execute(final Callable<T> callable) throws Exception {
int attempts = 0;
boolean shouldRetry;
boolean attemptFailed = false;
Expand Down
18 changes: 9 additions & 9 deletions src/test/java/io/github/alexo/retrier/RetrierTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class RetrierTest {
private static final int MAX_ATTEPTS = 10;
@Mock
private Callable<Object> callable;
private Retrier victim;
private Retrier<Object> victim;

@BeforeMethod
public void setUp() {
Expand All @@ -33,11 +33,11 @@ public void setUp() {
victim = createDefaultBuilder().build();
}

private Retrier.Builder createDefaultBuilder() {
return new Retrier.Builder().withStopStrategy(stopAfter(MAX_ATTEPTS));
private Retrier.Builder<Object> createDefaultBuilder() {
return new Retrier.Builder<Object>().withStopStrategy(stopAfter(MAX_ATTEPTS));
}

private Retrier withCustomExceptionFailedStrategy() {
private Retrier<Object> withCustomExceptionFailedStrategy() {
return createDefaultBuilder().withFailedRetryStrategy(CustomException.class::isInstance).build();
}

Expand All @@ -46,22 +46,22 @@ private static class CustomException extends Exception {

@Test(expectedExceptions = NullPointerException.class)
public void cannotBuildWithNullFailedRetryStrategy() {
new Retrier.Builder().withFailedRetryStrategy(null);
new Retrier.Builder<>().withFailedRetryStrategy(null);
}

@Test(expectedExceptions = NullPointerException.class)
public void cannotBuildWithNullResultRetryStrategy() {
new Retrier.Builder().withResultRetryStrategy(null);
new Retrier.Builder<>().withResultRetryStrategy(null);
}

@Test(expectedExceptions = NullPointerException.class)
public void cannotBuildWithNullStopStrategy() {
new Retrier.Builder().withStopStrategy(null);
new Retrier.Builder<>().withStopStrategy(null);
}

@Test(expectedExceptions = NullPointerException.class)
public void cannotBuildWithNullWaitStrategy() {
new Retrier.Builder().withWaitStrategy(null);
new Retrier.Builder<>().withWaitStrategy(null);
}

@Test
Expand Down Expand Up @@ -126,7 +126,7 @@ public void shouldInvokeOnlyOnceWhenFirstCallDoesNotFail() throws Exception {
public void shouldRetryWhenUsingCustomRetryStrategy() throws Exception {
final int retryResult = 0;
// retry as long as result is 0
victim = new Retrier.Builder().withResultRetryStrategy(i -> i.equals(retryResult))
victim = new Retrier.Builder<>().withResultRetryStrategy(i -> i.equals(retryResult))
.withStopStrategy(stopAfter(MAX_ATTEPTS)).build();
doAnswer(i -> retryResult).doAnswer(i -> retryResult).doAnswer(i -> retryResult + 1)
.when(callable).call();
Expand Down