cats-retry icon indicating copy to clipboard operation
cats-retry copied to clipboard

Create version for exponential backoff with max capped delay

Open Ssstlis opened this issue 4 years ago • 3 comments

Similar to akka i wanna have ability to create exponential backoff retry policy with max capped delay just for keep logical delay, not like 10 minutes. I can create PR by myself if you don't mind :)

Ssstlis avatar Mar 30 '20 17:03 Ssstlis

I'm not sure what you mean by "logical delay". Do you have a link to the Akka docs for this feature? Or some sample code to show what it looks like in Akka?

cb372 avatar Mar 31 '20 09:03 cb372

In akka we have this functionality:

RestartSource.onFailuresWithBackoff(
      minBackoff = 3.seconds,
      maxBackoff = 30.seconds,
      randomFactor = 0.2,
      maxRestarts = -1
    )(() => YourStream)

Link to api docs

When we migrate to fs2 i wrote this policy for reproduce this behavior in cats-retry:

  def exponentialRandomBackoff[M[_] : Applicative](
    minBackoff: FiniteDuration,
    maxBackoff: FiniteDuration,
    randomFactor: Double,
    maxRestarts: Int
  ): RetryPolicy[M] =
    RetryPolicy.liftWithShow[M]({
      status =>
        if (maxRestarts == -1 || status.retriesSoFar < maxRestarts) {
          val delay =
            if (status.previousDelay.exists(_ >= maxBackoff))
              maxBackoff
            else {
              //Copy-paste from RetryPolicies.safeMultiply
              val durationNanos = BigInt(minBackoff.toNanos)
              val resultNanos = durationNanos * BigInt(Math.pow(2, status.retriesSoFar.toDouble).toLong)
              val safeResultNanos = resultNanos min BigInt(Long.MaxValue)
              val duration_ = (FiniteDuration(safeResultNanos.toLong, TimeUnit.NANOSECONDS) *
                (1.0 + Random.nextDouble() * randomFactor)) min maxBackoff

              FiniteDuration(duration_.toNanos, TimeUnit.NANOSECONDS)
            }
          DelayAndRetry(delay)
        } else
          GiveUp
    }, show"exponentialRandomBackoff(minBackoff=$minBackoff, maxBackoff=$maxBackoff, randomFactor=$randomFactor, maxRestarts=$maxRestarts)")

Also i reference path of this code issue in #188

Ssstlis avatar Mar 31 '20 10:03 Ssstlis

I rewrite this example with built-in functions:

  def exponentialRandomBackoff[M[_] : Applicative](
    minBackoff: FiniteDuration,
    maxBackoff: FiniteDuration,
    randomFactor: Double,
    maxRestarts: Int
  ): RetryPolicy[M] =
    RetryPolicy
      .withShow[M](
        { status =>
          RetryPolicies.exponentialBackoff[M](minBackoff).decideNextRetry(status).map {
            case GiveUp => GiveUp
            case DelayAndRetry(delay) =>
              val nextDelay = delay * (1.0 + Random.nextDouble() * randomFactor)
              DelayAndRetry(FiniteDuration(nextDelay.toNanos, TimeUnit.NANOSECONDS))
          }
        },
        show"exponentialRandomBackoff(minBackoff=$minBackoff, randomFactor=$randomFactor)"
      )
      .meet(RetryPolicies.constantDelay(maxBackoff))
      .join(
        if (maxRestarts == -1)
          RetryPolicies.constantDelay[M](Duration.Zero)
        else
          RetryPolicies.limitRetries[M](maxRestarts)
      )

So i think that we can put something like this in lib would be helpful for devs who migrate from akka-streams to fs2.

Ssstlis avatar Mar 31 '20 12:03 Ssstlis