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:

12 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. Great Article… I love to read your articles because your writing style is too good, its is very very helpful for all of us and I never get bored while reading your article because, they are becomes a more and more interesting from the starting lines until the end.
    msbi online training

    ReplyDelete
  8. You blog post is just completely quality and informative. Many new facts and information which I have not heard about before. Keep sharing more blog posts.

    Tableau online training

    ReplyDelete
  9. Nice post. By reading your blog, i get inspired and this provides some useful information. Thank you for posting this exclusive post for our vision. 

    ReactJS Online Training

    ReplyDelete
  10. Great Article… I love to read your articles because your writing style is too good, its is very very helpful for all of us and I never get bored while reading your article because, they are becomes a more and more interesting from the starting lines until the end.
    Sql server dba online training

    ReplyDelete
  11. It is amazing and wonderful to visit your site.Thanks for sharing this information,this is useful to me...
    Your good knowledge and kindness in playing with all the pieces were very useful. I don’t know what I would have done if I had not encountered such a step like this..
    Oracle DBA Online Training

    ReplyDelete