Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Retry with exponential backoff
  • Loading branch information
AnaghaSasikumar committed Nov 25, 2018
commit fd7eca290891d4ea07c8ddd42f36403e8bc3ad84
16 changes: 16 additions & 0 deletions retry/src/main/java/com/iluwatar/retry/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public static void main(String[] args) throws Exception {
noErrors();
errorNoRetry();
errorWithRetry();
errorWithRetryExponentialBackoff();
}

private static void noErrors() throws Exception {
Expand Down Expand Up @@ -102,4 +103,19 @@ private static void errorWithRetry() throws Exception {
+ "the result %s after a number of attempts %s", customerId, retry.attempts()
));
}

private static void errorWithRetryExponentialBackoff() throws Exception {
final RetryExponentialBackoff<String> retry = new RetryExponentialBackoff<>(
new FindCustomer("123", new CustomerNotFoundException("not found")),
6, //6 attempts
30000, //30 s max delay between attempts
e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())
);
op = retry;
final String customerId = op.perform();
LOG.info(String.format(
"However, retrying the operation while ignoring a recoverable error will eventually yield "
+ "the result %s after a number of attempts %s", customerId, retry.attempts()
));
}
}
115 changes: 115 additions & 0 deletions retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package com.iluwatar.retry;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;

/**
* Decorates {@link BusinessOperation business operation} with "retry" capabilities.
*
* @author George Aristy ([email protected])
* @param <T> the remote op's return type
*/
public final class RetryExponentialBackoff<T> implements BusinessOperation<T> {
private final BusinessOperation<T> op;
private final int maxAttempts;
private final long maxDelay;
private final AtomicInteger attempts;
private final Predicate<Exception> test;
private final List<Exception> errors;

/**
* Ctor.
*
* @param op the {@link BusinessOperation} to retry
* @param maxAttempts number of times to retry
* @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions
* will be ignored if no tests are given
*/
@SafeVarargs
public RetryExponentialBackoff(
BusinessOperation<T> op,
int maxAttempts,
long maxDelay,
Predicate<Exception>... ignoreTests
) {
this.op = op;
this.maxAttempts = maxAttempts;
this.maxDelay = maxDelay;
this.attempts = new AtomicInteger();
this.test = Arrays.stream(ignoreTests).reduce(Predicate::or).orElse(e -> false);
this.errors = new ArrayList<>();
}

/**
* The errors encountered while retrying, in the encounter order.
*
* @return the errors encountered while retrying
*/
public List<Exception> errors() {
return Collections.unmodifiableList(this.errors);
}

/**
* The number of retries performed.
*
* @return the number of retries performed
*/
public int attempts() {
return this.attempts.intValue();
}

@Override
public T perform() throws BusinessException {
do {
try {
return this.op.perform();
} catch (BusinessException e) {
this.errors.add(e);

if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) {
throw e;
}

try {
Random rand = new Random();
long testDelay = (long) Math.pow(2, this.attempts()) * 1000 + rand.nextInt(1000);
long delay = testDelay < this.maxDelay ? testDelay : maxDelay;
Thread.sleep(delay);
} catch (InterruptedException f) {
//ignore
}
}
}
while (true);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package com.iluwatar.retry;

import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

/**
* Unit tests for {@link Retry}.
*
* @author George Aristy ([email protected])
*/
public class RetryExponentialBackoffTest {
/**
* Should contain all errors thrown.
*/
@Test
public void errors() throws Exception {
final BusinessException e = new BusinessException("unhandled");
final RetryExponentialBackoff<String> retry = new RetryExponentialBackoff<>(
() -> {
throw e;
},
2,
0
);
try {
retry.perform();
} catch (BusinessException ex) {
//ignore
}

assertThat(
retry.errors(),
hasItem(e)
);
}

/**
* No exceptions will be ignored, hence final number of attempts should be 1 even if we're asking
* it to attempt twice.
*/
@Test
public void attempts() {
final BusinessException e = new BusinessException("unhandled");
final RetryExponentialBackoff<String> retry = new RetryExponentialBackoff<>(
() -> {
throw e;
},
2,
0
);
try {
retry.perform();
} catch (BusinessException ex) {
//ignore
}

assertThat(
retry.attempts(),
is(1)
);
}

/**
* Final number of attempts should be equal to the number of attempts asked because we are
* asking it to ignore the exception that will be thrown.
*/
@Test
public void ignore() throws Exception {
final BusinessException e = new CustomerNotFoundException("customer not found");
final RetryExponentialBackoff<String> retry = new RetryExponentialBackoff<>(
() -> {
throw e;
},
2,
0,
ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass())
);
try {
retry.perform();
} catch (BusinessException ex) {
//ignore
}

assertThat(
retry.attempts(),
is(2)
);
}
}