Ramon J Romero y Vigil Ramon J Romero y Vigil - 1 month ago 9
Scala Question

Scala Recursive Function with Future Return Type

I am writing a recursive retry function in scala where I want to know if there was a runtime error with the future creation. If there is then future instantiation is retried.

Imagine I have a database query function:

dbLookup(userId : UserId) : Option[UserName] = ???


The retry would look something of the form:

retry[T](f : () => Future[Option[T]],
notifyFailure : (t : Throwable) => Unit,
n : Int) : Future[Option[T]] = {
if(n <= 0) { Future{None} }
else {
val fut = f()
if(f.resultsInException) { //I don't know how to write this
notifyFailure(f.exception)
retry(f, notifyFailure, n-1) //try again
}
else {
f.originalValueAsFuture
}
}
}


How can this future functionality be implemented and allow for tail recursion optimization?

This function could be used to retry the database 10 times for a user if the execution context keeps throwing an exception when I try to create a Future:

val userNameAfterRetries =
retry(() => Future{dbLookup("1234")},
(t) => system error (s"future creation error : $t"),
10)


Note: this is sort of possible with
Future.fallbackTo
, but unfortunately fallbackTo takes in a
Future[T]
rather than a
() => Future[T]
. This is important because using fallbackTo would result in retrying at least 1 extra times even if the first attempt was successful.

Thank you in advance for your consideration and response.

Answer

How about this?

  def retry[T](f: () => Future[Option[T]],
               notifyFailure: Throwable => Unit,
               n: Int)(implicit ec : ExecutionContext): Future[Option[T]] = {
    if (n <= 0) Future.failed(new RuntimeException("Exceeded number of allowed retries"))
    else f().recoverWith { case originalError => notifyFailure(originalError); retry(f, notifyFailure, n - 1) }
  }

Update on tail recursion: The nature of Future is asynchronous so unless you want to await for result, I do not quite see it being possible to make it @tailrec because you will have to use recursion in callback.

Also practical note: if you know it is always ~10 retries, I would not be afraid of recursion.