1

I'm trying to develop a control structure that retries on some declared exceptions but throw others. The control structure works nicely but I have problem checking if the exception caught belongs to the types of declared exceptions. In more generic words, how do I check if a parameter is one of the declared list of type parameters?

Define a retry control structure:

def retry[T, R](times:Int=3, delay:Int=1000)(ex:Class[_<:Exception]*)(t:T)(block:T=>R):R = {
try {
  block(t)
}
catch {
  case e:Exception if (isOneOf(e, ex:_*) && times>0) => {
    println(s"Exception $e happened, sleep $delay milliseconds")
    Thread.sleep(delay)
    println(s"$times times remain for retry before give up")
    retry(times-1, delay)(ex:_*)(t)(block)
  }
  case e:Throwable => {
    println(s"Exception $e is not handled")
    throw e
  }
}

}

define isOneOf function to check the declared and runtime exception type

def isOneOf[T:scala.reflect.ClassTag](obj:T, cs:Class[_]*) = {
  val dc = obj.getClass
  val rc = scala.reflect.classTag[T].runtimeClass
  cs.exists(c=>c.isAssignableFrom(dc) || c.isAssignableFrom(rc))
}

define a function that throws multiple exceptions

def d(str:String) = {
  val r = Math.random()
  println(r)

  if (r>0.6) throw new IllegalArgumentException
  else if (r>0.4) throw new java.io.IOException
  else if (r>0.2) throw new UnsupportedOperationException
  else println(str)
}

and I can call the function with retry as:

retry(3, 1000)(classOf[IllegalArgumentException], classOf[java.io.IOException])("abc"){
  x=> d(x)
}

I want to retry on IllegalArgumentException and IOException, but will throw UnsupportedOperationException.

My goal is to call the function like this:

retry(3, 1000)[IllegalArgumentException, java.io.IOException]("abc"){
  x=> d(x)
}

For the retry structure, the list of declared exceptions is passed in dynamically at runtime. So multiple exception case statement does not work for me. When an exception is caught, I match it with generic exception, check the exception type with the isOneOf function. Ideally the function will take a sequence of types, instead of a sequence of classes. How do I pass in a sequence of exception types, rather than a sequence of classes and check the caught exception against the sequence of types?

JJJ
  • 32,902
  • 20
  • 89
  • 102
sabz
  • 21
  • 2
  • 1
    Actually there are quite a lot of implementations of retry with delay. One of them - https://gist.github.com/Mortimerp9/5430595 – Bask.ws Dec 20 '13 at 11:42
  • 1
    You can't use a variable number of type identifiers on the `retry` method. That's just not how they were meant to be used. You're probably stuck passing in the list of supported retryable exceptions. – cmbaxter Dec 20 '13 at 13:14

1 Answers1

2

What do you think about replacing the list of exceptions with the function that knows which of exceptions are recoverable (and which you can build dynamically):

def retry[T, R](times: Int = 3, delay: Int = 1000)(t: T)(block: T => R)(recoverable: Throwable => Boolean): R = {

  Try(block(t)) match {
    case Success(value) => value
    case Failure(throwable) => if (recoverable(throwable)) retry(times - 1, delay)(t)(block)(recoverable) else throw throwable
  }

}

recoverable function could look like this:

def recoverable(throwable: Throwable): Boolean = throwable match {
  case exc @ (_: IllegalArgumentException | _: IOException) => true
  case _ => false
}
iuriisusuk
  • 424
  • 5
  • 15
  • `if(recoverable(throwable) && times >= 0) ...` in the if statement. – aepurniet Dec 20 '13 at 19:14
  • So a recoverable function and the pattern binder have to be defined in order to use the retry structure. case exc @(_: IllegalArgumentException | _: IOException) => true is equivalent to case exc:IllegalArgumentException => true case exc:IOException => true and the two exceptions have to be statically coded in the recoverable function. This does not really simplify the code. With the class varargs definition I can build the list of exceptions to retry at runtime: val recoverable:Seq[Class[_<:Exception]] = compute and call retry with retry()(recoverable)("abc") { x=>d(x) } – sabz Dec 21 '13 at 07:04
  • You are right, you should hard code them, but won't you do the same with 'List'? I mean you will create outside the 'retry' function a list with predefined exceptions, and in the same way you will dynamically build a function with those exceptions set. So the idea is to make this list configurable outside the 'retry', what you can do with both. What do you think? – iuriisusuk Dec 21 '13 at 08:43