2

I am trying to write a retry mechanism in java, that reties a function after 3 minutes in case of failure, and retries up to maximum 3 times. I do not want to use Thread.Sleep, and instead I was thinking about using ScheduledExecutorService. I am trying to figure out what would be a good implementation for it. It seems executor.schedule() does not the runnable inside the runnable.

I was thinking something like this:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
final int count = 1;
final int MAX_RETRY = 3;
Runnable runnable = () -> {
   try {
     //This function can fail and throw FunctionException
     callMyFunction();
   }
   catch (FunctionException e) {
     if (++count <= MAX_RETRY) {
        executor.schedule(runnable, 30*60*1000);
     }
   }
};

executor.execute(runnable);
Mr.President
  • 125
  • 2
  • 12
  • "*It seems executor.schedule() does not the runnable inside the runnable.*" - How did you determin this? --- The logic is flawed. Every execution of the `runnable` will have its own `count`, always starting at `0`, thus it will never un-schedulel the task. – Turing85 Feb 25 '21 at 20:09
  • Correct. I just edited the code and moved the counter outside of the runnable (and lets assume we hit the condition of rescheduling the service). I am more concerned about what is the correct way of scheduling a runnable and reschedule it in case of failure. – Mr.President Feb 25 '21 at 20:14
  • The code provided does not compile. For one, `count` is not effectively final. For another, `runnable` is uninitialized. For a third, there is no method `schedule(Runnable, long)` in`ScheduledExecutorService`. – Turing85 Feb 25 '21 at 20:19
  • Thats right. The count is final now. My question is more about runnable is not initialized. I was getting that error as well, any idea how to provide a solution for that? – Mr.President Feb 25 '21 at 20:22
  • "*The count is final now.*" - [No, it is not](https://ideone.com/iEOWSp) (notice the compilation error). --- For a working example, please take a look at this [Ideone demo](https://ideone.com/HDwwmF). I used some dirty hacks to get it working. Cleaner solution would be to use a proper `Queue<...>` that is polled by an `ExecutorService` periodically. Also, the tasks in the queue should define their onw state, i.e. the `count` of retries, such that the dirty array-hack is not needed. – Turing85 Feb 25 '21 at 20:31
  • Thanks for the solution. What is the service.shutdown() at the end? I was trying to avoid the Tread.sleep in my code, but it seems we are again using it :) – Mr.President Feb 25 '21 at 20:44
  • The `service.shutdown()` and `Thread.sleep(...)` are only there so the program terminates (properly). In essence, the `service` keeps running if one does not terminates it, hence the whole program will not terminate. The `sleep(...)` is to prevent premature termination, such that the (failing) calls have time to re-submit. After `shutdown()` is called, the `service` will no longer accept new tasks. – Turing85 Feb 25 '21 at 20:46
  • I see. You also mentioned a better way would be using a Queue, but I am not sure how that can be done, could you please provide some hints/implementation for that approach? – Mr.President Feb 25 '21 at 20:53
  • I already gave you some hints. You are stretching your luck. We are not a code-writing service. Please read some resources and/or try something yourself. – Turing85 Feb 25 '21 at 20:56
  • Thank a lot for the comments, I just posted a code snippet which seems to be working. – Mr.President Feb 26 '21 at 01:31

2 Answers2

0

I would suggest you use the spring-retry library instead of writing your own implementation.

Create a RetryTemplate instance. Sample code here - https://github.com/innovationchef/batchpay/blob/master/src/main/java/com/innovationchef/service/PayApiRetryTemplate.java

Then call you method like this -

retryTemplate.execute(arg -> callMyFunction());
Innovationchef
  • 338
  • 2
  • 10
  • You should always include relevant excerpts from links because links can stop working after a while and the answer should provide the relevant information even if the link isn't working anymore. – BeWu Feb 25 '21 at 20:14
  • 1
    It is not a spring project, and I would like to write it with ScheduledExecutorService. Any idea about that? – Mr.President Feb 25 '21 at 20:16
  • I think you can add spring-retry as a dependency and use it as shown in sample. It does have to be a spring or a spring-boot project. Instead of injecting the RetryTemplate as a bean in the context, just do a new RetryTemplate() and override those methods and you should be good to go. – Innovationchef Feb 25 '21 at 20:23
0

This code seems to work, and resolves the issue I had. I could not use lambda to implement my Runnable function, as in lambda there is no way to have "this" reference the instance created by a lambda expression in this line:

scheduler.schedule(this, 1, TimeUnit.SECONDS);

Any idea how to fix that?

Here is the entire code:

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
AtomicInteger counter = new AtomicInteger(1);
final int MAX_RETRY = 3;
scheduler.execute(new Runnable() {
   @Override
   public void run () {
   try {
     System.out.println("Attempt number: " + counter.get());
     functionCall();
     scheduler.shutdown();
   }
   catch (Exception e) {
      if (counter.get() < MAX_RETRY) {
            System.out.println("Attempt number: " + counter.getAndIncrement() + " failed.");
            scheduler.schedule(this, 1, TimeUnit.SECONDS);
      }
      else {
         System.out.println("Error message: " + e.getMessage());
         scheduler.shutdown();
      }
   }
 }
 });
 }
    
 public static void functionCall() {
     throw new RuntimeException("Does not work");
 }

Thanks @Turing85 for the help and useful comments.

Mr.President
  • 125
  • 2
  • 12