Reputation: 335
I'm new to Scala.
I want to retry code1
only if it timeout. If another error occurs, I'll handle it in another way. If timeout, I want to try again, for example, 3 times. What is the proper way to doing this?
I'm not sure if using something like code2
is the best way of doing this on Scala. Maybe there are already something better. Is there some cleaner way for breaking? Should I use Await.ready instead of result?
Im also not sure if I should use Await.ready or Await.result.
code1:
Await.result(myMethod(), Duration(120, "seconds"))
code2:
breakable {
for(_ <- 0 to 3) {
Try(Await.result(myMethod(), Duration(120, "seconds"))) match {
case Success(value) => {
// do something
break()
}
case Failure(_) => // how can I now if it is timeout?
}
}
}
Upvotes: 1
Views: 551
Reputation: 2838
You could try to apply the following approach for repeating evaluation
.
The following snippet defines a generic retry
method: this method takes in an argument, evaluates it within a try - catch
block, and re-executes it on failure with a maximum number of attempts.
Making retry
take a by-name parameter
is what allows it to repeat evaluation of the myMethod block
where necessary.
object Test {
def retry[T](max: Int)(f: => T): T = {
var tries = 0
var result: Option[T] = None
while (result == None) {
try {
result = Some(f)
}
catch {
case e: Throwable => tries += 1
if (tries > max) throw e
else {
println(s"failed, retry #$tries")
}
}
}
result.get
}
def myMethod(n: Int): Int = {
val num = scala.util.Random.nextInt(10)
if(n < num) throw new Exception
else num
}
def main(args: Array[String]): Unit = {
println(retry(max = 5){myMethod(3)})
/*
failed, retry #1
failed, retry #2
3
*/
println(retry(max = 5){myMethod(-1)})
/*
failed, retry #1
failed, retry #2
failed, retry #3
failed, retry #4
failed, retry #5
Exception in thread "main" java.lang.Exception
*/
}
}
NOTE
myMethod
function could take a new parameter timeout to introduce some time limit for each function call.
def myMethod(n: Int, timeout: Long): Int = {
var num = scala.util.Random.nextInt(10)
val current = System.currentTimeMillis()
var running = System.currentTimeMillis()
while (running - current <= timeout && n < num) {
num = scala.util.Random.nextInt(10)
running = System.currentTimeMillis()
}
println(s"time goes on ${running - current}")
if(n < num) throw new Exception
else num
}
// retry function doesn't change
println(retry(5){myMethod(5,5000/*five seconds*/)})
/*
time goes on 5001
failed, retry #1
time goes on 5001
1
*/
println(retry(5){myMethod(-1,5000/*five seconds*/)})
/*
time goes on 5001
failed, retry #1
time goes on 5001
failed, retry #2
time goes on 5001
failed, retry #3
time goes on 5001
failed, retry #4
time goes on 5001
failed, retry #5
time goes on 5001
Exception in thread "main" java.lang.Exception
*/
Upvotes: 1
Reputation: 51271
Waiting on a Future
makes the whole Future
-thing rather pointless. That being said, there are 3 situations that should be handled:
Future
completes successfullyimport scala.concurrent.duration._
import scala.concurrent.{Future, Await, TimeoutException}
import scala.util.{Try, Success, Failure}
// v--max number of attempts
LazyList.fill(4)(Try(Await.result(myMethod(), 120.seconds)))
.dropWhile{case Failure(_:TimeoutException) => true
case _ => false
}.headOption.fold{
//every attempt timed out
}{
case Success(value) => //do something
case Failure(ex:Throwable) => //ex is non-timeout exception
}
Note: LazyList
is a Scala 2.13.x collection. Every element in the list is evaluated only if/when it's needed, so if the 1st one succeeds then none of the other futures will be launched.
Upvotes: 3
Reputation: 229
You could use recursion to implement a simple retry mechanism. Matching on the TimeoutException
failure will allow you to deal specifically with the timeout case.
Here is an example below, with the assumption that myMethod
returns a Future[String]:
def myMethod(): Future[String] = ???
val maxAttempts = 3
def retryOnTimeout(attempt: Int = 0): String = {
Try(Await.result(myMethod(), Duration(120, "seconds"))) match {
case Success(result) => result
case Failure(_: TimeoutException) if attempt < maxAttempts => retryOnTimeout(attempt + 1)
case Failure(err) => throw err
}
}
retryOnTimeout
could be made more generic to allow it to be used in more than one place, with the function being passed in as a parameter.
Upvotes: 2