ajaymysore
ajaymysore

Reputation: 241

wrapping around generic callback function with different signatures

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?

Upvotes: 1

Views: 437

Answers (1)

Peter Neyens
Peter Neyens

Reputation: 9820

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)

Upvotes: 3

Related Questions