-
-
Save viktorklang/9414163 to your computer and use it in GitHub Desktop.
import scala.concurrent.duration._ | |
import scala.concurrent.ExecutionContext | |
import scala.concurrent.Future | |
import akka.pattern.after | |
import akka.actor.Scheduler | |
/** | |
* Given an operation that produces a T, returns a Future containing the result of T, unless an exception is thrown, | |
* in which case the operation will be retried after _delay_ time, if there are more possible retries, which is configured through | |
* the _retries_ parameter. If the operation does not succeed and there is no retries left, the resulting Future will contain the last failure. | |
**/ | |
def retry[T](op: => T, delay: FiniteDuration, retries: Int)(implicit ec: ExecutionContext, s: Scheduler): Future[T] = | |
Future(op) recoverWith { case _ if retries > 0 => after(delay, s)(retry(op, delay, retries - 1)) } |
@chadselph @viktorklang can you license these snippets explicitly? MIT would be nice.
@graingert Apologies, sadly Github doesn't notify when there are comments to Gists.
I don't think my snippet is complex enough to give a license at all.
I might be a bit late to this party, but I felt like it will be a useful contribution.
First of all, a very useful snippet and a nice bunch of follow-ups. However, if it is defined the following way:
def retry[T](f: => Future[T], delays: Seq[FiniteDuration])(implicit ec: ExecutionContext, s: Scheduler): Future[T] = {
f recoverWith { case _ if delays.nonEmpty => after(delays.head, s)(retry(f, delays.tail) }
}
The f
parameter, which is passed by-name, will get evaluated when you call f recoverWith
. After this point, if the future indeed fails, you will just end up passing the same failed future as many times as you have delays. So it won't really retry - it will just waste some time.
The following is a potential work-around:
def retry[T](f: () => Future[T], delays: Seq[FiniteDuration])(implicit ec: ExecutionContext, s: Scheduler): Future[T] = {
f() recoverWith { case _ if delays.nonEmpty => after(delays.head, s)(retry(f, delays.tail)
}
Of course, you'll have to modify the way you call the method accordingly.
Looking at example at https://docs.scala-lang.org/tour/by-name-parameters.html - isn't the by-name parameter evaluated each time when accessed? The condition: => Boolean
would ne er evaluate to false
when it was true
initally. Or am I missing something?
Allow me to agree with Graingert that a license would be great to have.
May I suggest the Beerware license?
"BeerWare: If you have the time and money, send me a bottle of your favourite beer. If not, just send me a mail or something. Copy and use as you wish; just leave the author's name where you find it."
@nikolovivan - As @tadej-mali points out, the call-by-name parameter is evaluated each time it is referenced; it is not a thunk.
Note that the operation parameter (originally op, then f in the comments) should not have any side effects (e.g. logging), since they might be done several times. In some special situations this might not be a problem though.
@chadselph thanks !