There are many cases in which you may wish to retry an operation a certain number of times. Examples are database failures, network communication failures or file IO problems.
Approach 1
This is the traditional approach and involves a counter and a loop.
final int numberOfRetries = 5 ;
final long timeToWait = 1000 ;
for (int i=0; i<numberOfRetries; i++) {
//perform the operation
try {
Naming.lookup("rmi://localhost:2106/MyApp");
break;
}
catch (Exception e) {
logger.error("Retrying...",e);
try {
Thread.sleep(timeToWait);
}
catch (InterruptedException i) {
}
}
}
Approach 2
In this approach, we hide the retry counter in a separate class called RetryStrategy
and call it like this:
public class RetryStrategy
{
public static final int DEFAULT_NUMBER_OF_RETRIES = 5;
public static final long DEFAULT_WAIT_TIME = 1000;
private int numberOfRetries; //total number of tries
private int numberOfTriesLeft; //number left
private long timeToWait; //wait interval
public RetryStrategy()
{
this(DEFAULT_NUMBER_OF_RETRIES, DEFAULT_WAIT_TIME);
}
public RetryStrategy(int numberOfRetries, long timeToWait)
{
this.numberOfRetries = numberOfRetries;
numberOfTriesLeft = numberOfRetries;
this.timeToWait = timeToWait;
}
/**
* @return true if there are tries left
*/
public boolean shouldRetry()
{
return numberOfTriesLeft > 0;
}
/**
* This method should be called if a try fails.
*
* @throws RetryException if there are no more tries left
*/
public void errorOccured() throws RetryException
{
numberOfTriesLeft --;
if (!shouldRetry())
{
throw new RetryException(numberOfRetries +
" attempts to retry failed at " + getTimeToWait() +
"ms interval");
}
waitUntilNextTry();
}
/**
* @return time period between retries
*/
public long getTimeToWait()
{
return timeToWait ;
}
/**
* Sleeps for the duration of the defined interval
*/
private void waitUntilNextTry()
{
try
{
Thread.sleep(getTimeToWait());
}
catch (InterruptedException ignored) {}
}
public static void main(String[] args) {
RetryStrategy retry = new RetryStrategy();
while (retry.shouldRetry()) {
try {
Naming.lookup("rmi://localhost:2106/MyApp");
break;
}
catch (Exception e) {
try {
retry.errorOccured();
}
catch (RetryException e1) {
e.printStackTrace();
}
}
}
}
}
Approach 3
Approach 2, although cleaner, hasn't really reduced the number of lines of code we have to write. In the next approach, we hide the retry loop and all logic in a separate class called RetriableTask
. We make the operation that we are going to retry Callable
and wrap it in a RetriableTask
which then handles all the retrying for us, behind-the-scenes:
public class RetriableTask<T> implements Callable<T> {
private Callable<T> task;
public static final int DEFAULT_NUMBER_OF_RETRIES = 5;
public static final long DEFAULT_WAIT_TIME = 1000;
private int numberOfRetries; // total number of tries
private int numberOfTriesLeft; // number left
private long timeToWait; // wait interval
public RetriableTask(Callable<T> task) {
this(DEFAULT_NUMBER_OF_RETRIES, DEFAULT_WAIT_TIME, task);
}
public RetriableTask(int numberOfRetries, long timeToWait,
Callable<T> task) {
this.numberOfRetries = numberOfRetries;
numberOfTriesLeft = numberOfRetries;
this.timeToWait = timeToWait;
this.task = task;
}
public T call() throws Exception {
while (true) {
try {
return task.call();
}
catch (InterruptedException e) {
throw e;
}
catch (CancellationException e) {
throw e;
}
catch (Exception e) {
numberOfTriesLeft--;
if (numberOfTriesLeft == 0) {
throw new RetryException(numberOfRetries +
" attempts to retry failed at " + timeToWait +
"ms interval", e);
}
Thread.sleep(timeToWait);
}
}
}
public static void main(String[] args) {
Callable<Remote> task = new Callable<Remote>() {
public Remote call() throws Exception {
String url="rmi://localhost:2106/MyApp";
return (Remote) Naming.lookup(url);
}
};
RetriableTask<Remote> r = new RetriableTask<Remote>(task);
try {
r.call();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Also see:
References: