Monday, August 17, 2009

Retrying Operations in Java

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:

8 comments:

  1. This is great stuff. I would even suggest that RetriableTask could accept a RetryStrategy instance so as control how the/when the retry is handled, e.g., different tasks might need different strategies. For example a strategy might only retry on a specific set of possible exceptions and fail-fast on others.

    ReplyDelete
  2. In the last sample, it seems should not be while (true),
    It should be:
    while(numberOfTriesLeft)

    right?

    ReplyDelete
  3. Hi Fahd,
    I am currently looking at some code in one of our products and we find that part of is is derived from some code you
    posted on your blog : http://fahdshariff.blogspot.co.uk/2009/08/retrying-operations-in-java.html
    I need to know what terms your source code is licensed under, if you authored 100% of the code
    on this blog or if someone else authored it .
    Is the code licensed under a standard open source license? (which one?)
    If not , do you give us permission to perform, distribute, and creative derivative works.

    Thanks

    ReplyDelete
  4. Isn't approach 1 the cleanest and shortest?

    ReplyDelete
  5. behind-the-scenes:? what is the meaning of this say i have
    ExecutorService executor = Executors.newFixedThreadPool(10);
    executor.execute(retriableTask);

    if the 6 tasks in retry , when i m printing the getTaskCount() it is showing zero.
    when tasks are in retry , that means thread is occupied , if yes why pool is not showing the task count as 6.

    How can i control the parallel threads threshold , say i want to process only 10 threads including retry also.

    how can i see count of processing in retry
    Thread.sleep(timeToWait);

    ReplyDelete
  6. This was quite useful. Thank you

    ReplyDelete
  7. I am really very happy to find this particular site. I just wanted to say thank you for this huge read!! I absolutely enjoying every petite bit of it and I have you bookmarked to test out new substance you post.
    Software testing online training
    Software testing certification training
    Software testing online course
    Software testing training course

    ReplyDelete

  8. Body pain is a very common issue that we have to face in our daily life. When you face the issue of pain, it is best if you take the help of the doctor. But every time, you cannot get the help of the doctor. In those situations, to get quick and effective relief from pain, you take the help of the painkillers. Soma pill is an effective painkiller by using which you can get instant relief from your pain. This painkiller has Carisoprodol as the active ingredient. You can buy Soma 350 easily from the market. To enjoy the effects of this painkiller at an affordable price, buy Soma 350 mg online.
    Buy Soma online

    ReplyDelete