1

I am trying to learn Scala and it seems like a very powerful language so far, but some things seem hard to achieve, may be its just my learning curve. I have researched online for a few days but could not find a good solution to do what I want to do.

I have multiple methods (with different signatures, including return type) which I want to wrap around in a retry logic.

I want to keep calling a method for a predefined number of times until the method succeeds.

Here is an example:

def downloadLocal(memory: Boolean, userName: Name, version: Int): Int

def downloadRemote(
  memory: Boolean, userName: Name, masterNodeId: String, masterNodeId: String
): Pair(Int, Int)

I want wrap these two method inside the retry logic. Here is my attempt at the retry logic:

trait WithRetry{
  def withRetry(retry :Int){
    try{
      callBack
    }catch{
      case exception:Throwable =>
        if(retry>0){
          logger.warn(exec+" method failed with an exception. retry number is:" + retry);
          logger.warn(exception);
          withRetry(retry-1)
        }
        else{
          throw exception
        }
    }
  }
  def callBack():Any
}

The problem I have is that I am not able to find a clean way of wrapping around my methods (downloadRemote and downloadLocal) inside the retry logic.

Any suggestions/thoughts?

Peter Neyens
  • 9,770
  • 27
  • 33
ajaymysore
  • 241
  • 1
  • 4
  • 15

1 Answers1

3

You can just define a generic function :

import scala.util.{Try, Success, Failure}

def retry[T](times: Int)(block: => T): T = Try(block) match {
  case Success(result) => result
  case Failure(e) =>
    if (times > 0) {
      logger.warn(s"method failed with an exception, retry #$times")
      logger.warn(exception)
      retry(times - 1)(block)
    }
    else throw e
}

This is a tail recursive function, so it will be as efficient as calling the function in a for loop in Java.

However more idiomatic Scala would be to return a Try :

def retry2[T](times: Int)(block: => T): Try[T] = 
  Try(block) match {
    case Failure(e) if (times > 0) =>
      logger.warn(s"method failed with an exception, retry #$times")
      logger.warn(exception)
      retry2(times - 1)(block)
    case other => other
  }

Both versions could be used as :

def failRandomly: Int = 
  if (scala.util.Random.nextInt < 0.80) throw new Exception("boom")
  else 1

scala> retry(2)(failRandomly)
res0: Int = 1
scala> retry(2)(failRandomly)
java.lang.Exception: boom  // ...

scala> retry2(2)(failRandomly)
res1: scala.util.Try[Int] = Success(1)
scala> retry2(2)(failRandomly)
res2: scala.util.Try[Int] = Failure(java.lang.Exception: boom)
Peter Neyens
  • 9,770
  • 27
  • 33
  • Thanks a lot for your response. What is the suggested approach for making it portable, so that other methods can use this? Can I just wrap it around in a class (like below) and make the method static or is there a better way? **bold** public class RetryUtil{ def retry[T](times: Int)(block: => T): T = Try(block) match { case Success(result) => result case Failure(e) => if (times > 0) { logger.warn(s"method failed with an exception, retry #$retry") logger.warn(exception) retry(times - 1)(block) } else throw e } } **bold** – ajaymysore Jul 27 '15 at 23:03
  • You can use it with every function, eg: `retry2(5)(downloadLocal(true, "username", 2))` – Peter Neyens Jul 27 '15 at 23:07
  • In Scala we don't have static methods, we would create a method in an object. See [Difference between object and class in Scala](http://stackoverflow.com/questions/1755345/difference-between-object-and-class-in-scala) – Peter Neyens Jul 27 '15 at 23:10