Saturday, March 10, 2012

Java retry strategy

http://fahdshariff.blogspot.in/2009/08/retrying-operations-in-java.html

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.
   1: final int numberOfRetries = 5 ;
   2: final long timeToWait = 1000 ;
   3:  
   4: for (int i=0; i<numberOfRetries; i++) {
   5:  //perform the operation
   6:  try {
   7:   Naming.lookup("rmi://localhost:2106/MyApp");
   8:   break;
   9:  }
  10:  catch (Exception e) {
  11:   logger.error("Retrying...",e);
  12:   try {
  13:    Thread.sleep(timeToWait);
  14:   }
  15:   catch (InterruptedException i) {
  16:   }
  17:  }
  18: }

Approach 2
In this approach, we hide the retry counter in a separate class called RetryStrategy and call it like this:  


   1: public class RetryStrategy
   2: {
   3:  public static final int DEFAULT_NUMBER_OF_RETRIES = 5;
   4:  public static final long DEFAULT_WAIT_TIME = 1000;
   5:  
   6:  private int numberOfRetries; //total number of tries
   7:  private int numberOfTriesLeft; //number left
   8:  private long timeToWait; //wait interval
   9:  
  10:  public RetryStrategy()
  11:  {
  12:   this(DEFAULT_NUMBER_OF_RETRIES, DEFAULT_WAIT_TIME);
  13:  }
  14:  
  15:  public RetryStrategy(int numberOfRetries, long timeToWait)
  16:  {
  17:   this.numberOfRetries = numberOfRetries;
  18:   numberOfTriesLeft = numberOfRetries;
  19:   this.timeToWait = timeToWait;
  20:  }
  21:  
  22:  /**
  23:   * @return true if there are tries left
  24:   */
  25:  public boolean shouldRetry()
  26:  {
  27:   return numberOfTriesLeft > 0;
  28:  }
  29:  
  30:  /**
  31:   * This method should be called if a try fails.
  32:   *
  33:   * @throws RetryException if there are no more tries left
  34:   */
  35:  public void errorOccured() throws RetryException
  36:  {
  37:   numberOfTriesLeft --;
  38:   if (!shouldRetry())
  39:   {
  40:    throw new RetryException(numberOfRetries +
  41:      " attempts to retry failed at " + getTimeToWait() +
  42:      "ms interval");
  43:   }
  44:   waitUntilNextTry();
  45:  }
  46:  
  47:  /**
  48:   * @return time period between retries
  49:   */
  50:  public long getTimeToWait()
  51:  {
  52:   return timeToWait ;
  53:  }
  54:  
  55:  /**
  56:   * Sleeps for the duration of the defined interval
  57:   */
  58:  private void waitUntilNextTry()
  59:  {
  60:   try
  61:   {
  62:    Thread.sleep(getTimeToWait());
  63:   }
  64:   catch (InterruptedException ignored) {}
  65:  }
  66:  
  67:  public static void main(String[] args) {
  68:   RetryStrategy retry = new RetryStrategy();
  69:   while (retry.shouldRetry()) {
  70:    try {
  71:     Naming.lookup("rmi://localhost:2106/MyApp");
  72:     break;
  73:    }
  74:    catch (Exception e) {
  75:     try {
  76:      retry.errorOccured();
  77:     }
  78:     catch (RetryException e1) {
  79:      e.printStackTrace();
  80:     }
  81:    }
  82:   }
  83:  }
  84: }

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:


   1: public class RetriableTask<T> implements Callable<T> {
   2:  
   3:  private Callable<T> task;
   4:  public static final int DEFAULT_NUMBER_OF_RETRIES = 5;
   5:  public static final long DEFAULT_WAIT_TIME = 1000;
   6:  
   7:  private int numberOfRetries; // total number of tries
   8:  private int numberOfTriesLeft; // number left
   9:  private long timeToWait; // wait interval
  10:  
  11:  public RetriableTask(Callable<T> task) {
  12:   this(DEFAULT_NUMBER_OF_RETRIES, DEFAULT_WAIT_TIME, task);
  13:  }
  14:  
  15:  public RetriableTask(int numberOfRetries, long timeToWait,
  16:                       Callable<T> task) {
  17:   this.numberOfRetries = numberOfRetries;
  18:   numberOfTriesLeft = numberOfRetries;
  19:   this.timeToWait = timeToWait;
  20:   this.task = task;
  21:  }
  22:  
  23:  public T call() throws Exception {
  24:   while (true) {
  25:    try {
  26:     return task.call();
  27:    }
  28:    catch (InterruptedException e) {
  29:     throw e;
  30:    }
  31:    catch (CancellationException e) {
  32:     throw e;
  33:    }
  34:    catch (Exception e) {
  35:     numberOfTriesLeft--;
  36:     if (numberOfTriesLeft == 0) {
  37:      throw new RetryException(numberOfRetries +
  38:      " attempts to retry failed at " + timeToWait +
  39:      "ms interval", e);
  40:     }
  41:     Thread.sleep(timeToWait);
  42:    }
  43:   }
  44:  }
  45:  
  46:  public static void main(String[] args) {
  47:   Callable<Remote> task = new Callable<Remote>() {
  48:    public Remote call() throws Exception {
  49:     String url="rmi://localhost:2106/MyApp";
  50:     return (Remote) Naming.lookup(url);
  51:    }
  52:   };
  53:  
  54:   RetriableTask<Remote> r = new RetriableTask<Remote>(task);
  55:   try {
  56:    r.call();
  57:   }
  58:   catch (Exception e) {
  59:    e.printStackTrace();
  60:   }
  61:  }
  62: }
//creates a task which will retry 3 times with an interval of 5 seconds
RetriableTask r = new RetriableTask(3, 5000, new Callable(){
    public Object call() throws Exception{
        //put your code here
    }
});
r.call();


1 comment: