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

Allow the action to be redefined after errors

Open kubukoz opened this issue 4 years ago • 4 comments

Hi! We have a case where our action should not just be repeated on retries, but rather called in a slightly different way, based on the exact error received.

It could look like this (roughly):

case class VersionMismatch(v: Int) extends Throwable

def action(i: Int): IO[A] = ???

//ignored `isWorthRetrying`/`policy` parameters as they're irrelevant

//action: I => F[A]
//onError: E => F[Option[I]]
action.retryingOnSomeErrors(onError = {
  case VersionMismatch(v) => 
    logger.debug("Version mismatch, retrying...").as(v.some)
  case e => 
    logger.debug(s"Not retrying: $e").as(none)
}).apply(0) //initial value of `i`

As an alternative, the onError function could return the action to be used for retrying - this would probably be more flexible:

//action: F[A]
//onError: E => F[Option[F[A]]
action(0).retryingOnSomeErrors(onError = {
  case VersionMismatch(v) => 
    logger.debug("Version mismatch, retrying...").as(action(v).some)
  case e => 
    logger.debug(s"Not retrying: $e").as(none)
}

Our current workaround is creating a Ref to store the input, and getting it within the retryable action - but it's not a concurrent scenario and Ref seems like overkill (whereas underneath it would be relatively simple recursion).

kubukoz avatar Dec 21 '20 14:12 kubukoz

I'm in two minds on this one. It feels a little bit of a niche use case to me. I'd only add support for this if we can ensure we don't make life more difficult for people who don't need this feature.

If we're going to do it, I prefer the latter approach you suggested.

cb372 avatar Feb 18 '21 18:02 cb372

Hi, I'm aware it's been a while, but is there any implementation on this? Because I need something like this.

nachocodexx avatar Nov 04 '21 19:11 nachocodexx

I'm not aware of any. I would suggest that you use a Ref for now. IIRC this would look roughly like this:

def action(i: Int): IO[A] = ???

// 0 - initial value used as input to `action`
Ref[IO].of(0).flatMap { ref =>
  ref.get.flatMap(action).retryingOnSomeErrors(
    // skipped here: isWorthRetrying, policy
    onError = e => logger.error(e) *> ref.set(i + 1) //calculating next input as `i + 1`
  )
}

kubukoz avatar Nov 04 '21 22:11 kubukoz

@kubukoz thank you very much for taking the time to respond, and that solution is pretty neat, I'll try it.

nachocodexx avatar Nov 04 '21 22:11 nachocodexx