Retry a code block until it stops throwing exception.
Highly Configurable
- Set a maximum number of retries
- Set a deadline with
scala.concurrent.duration.Deadline
- Set a backoff duration after which to retry
- Determine which exception to stop retry
Uses scala.concurrent
, with no other dependencies. Based on Mortimerp9's gist. Added unit tests and backoff options. Refactoring and removed lint warts.
You must
- Provide an
ExecutionContext
before callingRetry
. For example,
import scala.concurrent.ExecutionContext.Implicits.global
Alternatively, if the task may result in many blocking threads, you can use a different thread pool:
import java.util.concurrent.Executors
val pool = Executors.newCachedThreadPool()
implicit val ec = ExecutionContext.fromExecutor(pool)
Now this pool is used by all futures when ec
is in scope.
- Set the maximum number of retries, given in the
maxRetry
argument. A negative number means an unlimited number of retries.
retry[Unit](maxRetry = 10){
send(email) // return type is Unit
}
import scala.concurrent.Future
val f: Future[Double] = retry[Double](maxRetry = 10){
api.get("stock/price/goog")
}
deadline
is an optional argument of type Option[Deadline]
. Default value is None
.
import scala.concurrent.duration.{Deadline, DurationInt, fromNow}
retry[Unit](
maxRetry = 10,
deadline = Option(2 hour fromNow)
){
send(email)
}
The default backoff function is unbounded and grows exponentially with the number of retries, in 100 millisecond steps. Alternatively, you can define your own backoff function and pass it in the backoff
argument.
The backoff function takes a retryCnt
argument of type Int
and returns a scala.concurrent.duration.Duration
. It will retry after this duration has passed.
import scala.concurrent.duration.{Duration, DurationInt}
def atMostOneDay(retryCnt: Int): Duration = {
val max: Duration = 24 hours
val d: Duration = Retry.exponentialBackoff(retryCnt)
if (d < max) d else max
}
retry[Unit](
maxRetry = 10,
backoff = atMostOneDay
){
send(email)
}
If you wish to avoid retrying at regular intervals, exponentialBackoffWithJitter
adds a random delay.
retry[Unit](
maxRetry = 10,
backoff = Retry.exponentialBackoffWithJitter
){
send(email)
}
To fail fast, you can determine which exception(s) to stop retrying. The giveUpOnThrowable
argument takes a function that returns a Boolean
value (true
to give up) for a given Throwable
.
def giveUp(t: Throwable): Boolean = t match {
case _: FileNotFoundException => true
case _: UnsupportedEncodingException => true
case _ => false
}
retry[String](
maxRetry = 10,
giveUpOnThrowable = giveUp
){
read(file)
}
By default, Retry
does not give up on any exception.