Skip to content

Retry something until it works. Set maximum #retries, deadline, backoff duration and more

License

Notifications You must be signed in to change notification settings

seahrh/concurrent-scala

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Retry in Scala

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.

Code Examples

You must

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.

You don't care about the result

retry[Unit](maxRetry = 10){
  send(email) // return type is Unit
}

You care about the result

import scala.concurrent.Future
val f: Future[Double] = retry[Double](maxRetry = 10){
  api.get("stock/price/goog")
}

Keep trying until a deadline has elapsed

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)
}

Customize the backoff function

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)
}

Give up on specific exceptions

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.

About

Retry something until it works. Set maximum #retries, deadline, backoff duration and more

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages